diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index c8c4f62..7838670 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -15,8 +15,7 @@ body:
description: Increase the chances of your issue being accepted by ensuring it has not been raised before.
options:
- label: I have checked "open" AND "closed" issues and this is not a duplicate
- validations:
- required: true
+ required: true
- type: textarea
id: description
attributes:
@@ -60,7 +59,6 @@ body:
options:
- "No"
- "Yes"
- default: 0
validations:
required: true
- type: textarea
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 9e41d2c..2f96d25 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -26,7 +26,6 @@ body:
options:
- "No"
- "Yes"
- default: 0
validations:
required: false
- type: textarea
diff --git a/ExplorerTabUtility/App.config b/ExplorerTabUtility/App.config
index 9d29ca9..832a850 100644
--- a/ExplorerTabUtility/App.config
+++ b/ExplorerTabUtility/App.config
@@ -7,17 +7,20 @@
-
- True
+
+ 0
True
-
+
True
-
- False
+
+ [{"Name":"Home","HotKeys":[91,69],"Scope":0,"Action":0,"Path":"","IsHandled":true,"IsEnabled":true,"Delay":0},{"Name":"Duplicate","HotKeys":[17,68],"Scope":1,"Action":1,"Path":null,"IsHandled":true,"IsEnabled":true,"Delay":0}]
+
+
+ True
diff --git a/ExplorerTabUtility/ExplorerTabUtility.csproj b/ExplorerTabUtility/ExplorerTabUtility.csproj
index f872794..1160fb6 100644
--- a/ExplorerTabUtility/ExplorerTabUtility.csproj
+++ b/ExplorerTabUtility/ExplorerTabUtility.csproj
@@ -4,10 +4,9 @@
WinExe
net7.0-windows;net481
enable
- true
True
Icon.ico
- 1.2.0
+ 1.3.0
latest
Explorer Tab Utility
w4po
@@ -24,7 +23,10 @@
+
+
+
diff --git a/ExplorerTabUtility/Forms/HotKeyProfileControl.Designer.cs b/ExplorerTabUtility/Forms/HotKeyProfileControl.Designer.cs
new file mode 100644
index 0000000..eaa0280
--- /dev/null
+++ b/ExplorerTabUtility/Forms/HotKeyProfileControl.Designer.cs
@@ -0,0 +1,455 @@
+namespace ExplorerTabUtility.Forms;
+
+partial class HotKeyProfileControl
+{
+ ///
+ /// 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 Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ pnlMain = new System.Windows.Forms.Panel();
+ btnCollapse = new MaterialSkin.Controls.MaterialButton();
+ splitter5 = new System.Windows.Forms.Splitter();
+ cbAction = new MaterialSkin.Controls.MaterialComboBox();
+ splitter4 = new System.Windows.Forms.Splitter();
+ cbScope = new MaterialSkin.Controls.MaterialComboBox();
+ splitter3 = new System.Windows.Forms.Splitter();
+ txtHotKeys = new MaterialSkin.Controls.MaterialTextBox();
+ splitter2 = new System.Windows.Forms.Splitter();
+ txtName = new MaterialSkin.Controls.MaterialTextBox();
+ splitter1 = new System.Windows.Forms.Splitter();
+ cbEnabled = new MaterialSkin.Controls.MaterialSwitch();
+ pnlMore = new System.Windows.Forms.Panel();
+ btnDelete = new MaterialSkin.Controls.MaterialButton();
+ splitter8 = new System.Windows.Forms.Splitter();
+ cbHandled = new MaterialSkin.Controls.MaterialSwitch();
+ splitter7 = new System.Windows.Forms.Splitter();
+ sDelay = new MaterialSkin.Controls.MaterialSlider();
+ splitter6 = new System.Windows.Forms.Splitter();
+ txtPath = new MaterialSkin.Controls.MaterialTextBox();
+ toolTip = new System.Windows.Forms.ToolTip(components);
+ pnlMain.SuspendLayout();
+ pnlMore.SuspendLayout();
+ SuspendLayout();
+ //
+ // pnlMain
+ //
+ pnlMain.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ pnlMain.Controls.Add(btnCollapse);
+ pnlMain.Controls.Add(splitter5);
+ pnlMain.Controls.Add(cbAction);
+ pnlMain.Controls.Add(splitter4);
+ pnlMain.Controls.Add(cbScope);
+ pnlMain.Controls.Add(splitter3);
+ pnlMain.Controls.Add(txtHotKeys);
+ pnlMain.Controls.Add(splitter2);
+ pnlMain.Controls.Add(txtName);
+ pnlMain.Controls.Add(splitter1);
+ pnlMain.Controls.Add(cbEnabled);
+ pnlMain.Location = new System.Drawing.Point(0, 0);
+ pnlMain.Margin = new System.Windows.Forms.Padding(0);
+ pnlMain.Name = "pnlMain";
+ pnlMain.Padding = new System.Windows.Forms.Padding(10, 0, 0, 0);
+ pnlMain.Size = new System.Drawing.Size(725, 37);
+ pnlMain.TabIndex = 0;
+ //
+ // btnCollapse
+ //
+ btnCollapse.AutoSize = false;
+ btnCollapse.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnCollapse.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnCollapse.Depth = 0;
+ btnCollapse.Dock = System.Windows.Forms.DockStyle.Left;
+ btnCollapse.HighEmphasis = true;
+ btnCollapse.Icon = null;
+ btnCollapse.Location = new System.Drawing.Point(687, 0);
+ btnCollapse.Margin = new System.Windows.Forms.Padding(5, 7, 5, 7);
+ btnCollapse.MouseState = MaterialSkin.MouseState.HOVER;
+ btnCollapse.Name = "btnCollapse";
+ btnCollapse.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnCollapse.Size = new System.Drawing.Size(36, 37);
+ btnCollapse.TabIndex = 4;
+ btnCollapse.Text = "ᐯ";
+ toolTip.SetToolTip(btnCollapse, "Show more.");
+ btnCollapse.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Outlined;
+ btnCollapse.UseAccentColor = true;
+ btnCollapse.UseVisualStyleBackColor = true;
+ btnCollapse.Click += BtnCollapse_Click;
+ //
+ // splitter5
+ //
+ splitter5.Location = new System.Drawing.Point(682, 0);
+ splitter5.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter5.MinSize = 120;
+ splitter5.Name = "splitter5";
+ splitter5.Size = new System.Drawing.Size(5, 37);
+ splitter5.TabIndex = 12;
+ splitter5.TabStop = false;
+ //
+ // cbAction
+ //
+ cbAction.AutoResize = false;
+ cbAction.BackColor = System.Drawing.Color.FromArgb(255, 255, 255);
+ cbAction.Depth = 0;
+ cbAction.Dock = System.Windows.Forms.DockStyle.Left;
+ cbAction.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
+ cbAction.DropDownHeight = 118;
+ cbAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ cbAction.DropDownWidth = 121;
+ cbAction.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel);
+ cbAction.ForeColor = System.Drawing.Color.FromArgb(222, 0, 0, 0);
+ cbAction.FormattingEnabled = true;
+ cbAction.Hint = "Action";
+ cbAction.IntegralHeight = false;
+ cbAction.ItemHeight = 29;
+ cbAction.Items.AddRange(new object[] { "Open", "ReopenClosed", "Duplicate", "Write" });
+ cbAction.Location = new System.Drawing.Point(529, 0);
+ cbAction.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ cbAction.MaxDropDownItems = 4;
+ cbAction.MouseState = MaterialSkin.MouseState.OUT;
+ cbAction.Name = "cbAction";
+ cbAction.Size = new System.Drawing.Size(153, 35);
+ cbAction.StartIndex = 0;
+ cbAction.TabIndex = 3;
+ toolTip.SetToolTip(cbAction, "What to do if the HotKeys got pressed.");
+ cbAction.UseTallSize = false;
+ cbAction.SelectedIndexChanged += CbAction_SelectedIndexChanged;
+ //
+ // splitter4
+ //
+ splitter4.Location = new System.Drawing.Point(525, 0);
+ splitter4.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter4.MinSize = 120;
+ splitter4.Name = "splitter4";
+ splitter4.Size = new System.Drawing.Size(4, 37);
+ splitter4.TabIndex = 13;
+ splitter4.TabStop = false;
+ //
+ // cbScope
+ //
+ cbScope.AutoResize = false;
+ cbScope.BackColor = System.Drawing.Color.FromArgb(255, 255, 255);
+ cbScope.Depth = 0;
+ cbScope.Dock = System.Windows.Forms.DockStyle.Left;
+ cbScope.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
+ cbScope.DropDownHeight = 118;
+ cbScope.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ cbScope.DropDownWidth = 121;
+ cbScope.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel);
+ cbScope.ForeColor = System.Drawing.Color.FromArgb(222, 0, 0, 0);
+ cbScope.FormattingEnabled = true;
+ cbScope.Hint = "Scope";
+ cbScope.IntegralHeight = false;
+ cbScope.ItemHeight = 29;
+ cbScope.Items.AddRange(new object[] { "Global", "FileExplorer" });
+ cbScope.Location = new System.Drawing.Point(390, 0);
+ cbScope.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ cbScope.MaxDropDownItems = 4;
+ cbScope.MouseState = MaterialSkin.MouseState.OUT;
+ cbScope.Name = "cbScope";
+ cbScope.Size = new System.Drawing.Size(135, 35);
+ cbScope.StartIndex = 0;
+ cbScope.TabIndex = 2;
+ toolTip.SetToolTip(cbScope, "Scope of the hotkeys, whether it's Global or only if the FileExplorer is focused.");
+ cbScope.UseTallSize = false;
+ cbScope.SelectedIndexChanged += CbScope_SelectedIndexChanged;
+ //
+ // splitter3
+ //
+ splitter3.Location = new System.Drawing.Point(386, 0);
+ splitter3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter3.MinSize = 120;
+ splitter3.Name = "splitter3";
+ splitter3.Size = new System.Drawing.Size(4, 37);
+ splitter3.TabIndex = 14;
+ splitter3.TabStop = false;
+ //
+ // txtHotKeys
+ //
+ txtHotKeys.AnimateReadOnly = true;
+ txtHotKeys.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ txtHotKeys.Depth = 0;
+ txtHotKeys.DetectUrls = false;
+ txtHotKeys.Dock = System.Windows.Forms.DockStyle.Left;
+ txtHotKeys.Font = new System.Drawing.Font("Roboto", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+ txtHotKeys.Hint = "HotKeys";
+ txtHotKeys.LeadingIcon = null;
+ txtHotKeys.LeaveOnEnterKey = true;
+ txtHotKeys.Location = new System.Drawing.Point(231, 0);
+ txtHotKeys.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ txtHotKeys.MaxLength = 50;
+ txtHotKeys.MouseState = MaterialSkin.MouseState.OUT;
+ txtHotKeys.Multiline = false;
+ txtHotKeys.Name = "txtHotKeys";
+ txtHotKeys.ReadOnly = true;
+ txtHotKeys.ShortcutsEnabled = false;
+ txtHotKeys.Size = new System.Drawing.Size(155, 36);
+ txtHotKeys.TabIndex = 1;
+ txtHotKeys.Text = "";
+ toolTip.SetToolTip(txtHotKeys, "HotKeys to listen for.");
+ txtHotKeys.TrailingIcon = null;
+ txtHotKeys.UseTallSize = false;
+ txtHotKeys.Enter += TxtHotKeys_Enter;
+ txtHotKeys.KeyDown += TxtHotKeys_KeyDown;
+ txtHotKeys.Leave += TxtHotKeys_Leave;
+ //
+ // splitter2
+ //
+ splitter2.Location = new System.Drawing.Point(227, 0);
+ splitter2.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter2.MinSize = 120;
+ splitter2.Name = "splitter2";
+ splitter2.Size = new System.Drawing.Size(4, 37);
+ splitter2.TabIndex = 15;
+ splitter2.TabStop = false;
+ //
+ // txtName
+ //
+ txtName.AnimateReadOnly = false;
+ txtName.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ txtName.Depth = 0;
+ txtName.Dock = System.Windows.Forms.DockStyle.Left;
+ txtName.Font = new System.Drawing.Font("Roboto", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+ txtName.Hint = "Name";
+ txtName.LeadingIcon = null;
+ txtName.LeaveOnEnterKey = true;
+ txtName.Location = new System.Drawing.Point(72, 0);
+ txtName.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ txtName.MaxLength = 50;
+ txtName.MouseState = MaterialSkin.MouseState.OUT;
+ txtName.Multiline = false;
+ txtName.Name = "txtName";
+ txtName.Size = new System.Drawing.Size(155, 36);
+ txtName.TabIndex = 0;
+ txtName.Text = "";
+ toolTip.SetToolTip(txtName, "Name of the profile.");
+ txtName.TrailingIcon = null;
+ txtName.UseTallSize = false;
+ txtName.TextChanged += TxtName_TextChanged;
+ //
+ // splitter1
+ //
+ splitter1.Location = new System.Drawing.Point(68, 0);
+ splitter1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter1.MinSize = 50;
+ splitter1.Name = "splitter1";
+ splitter1.Size = new System.Drawing.Size(4, 37);
+ splitter1.TabIndex = 16;
+ splitter1.TabStop = false;
+ //
+ // cbEnabled
+ //
+ cbEnabled.AutoSize = true;
+ cbEnabled.Checked = true;
+ cbEnabled.CheckState = System.Windows.Forms.CheckState.Checked;
+ cbEnabled.Depth = 0;
+ cbEnabled.Dock = System.Windows.Forms.DockStyle.Left;
+ cbEnabled.Location = new System.Drawing.Point(10, 0);
+ cbEnabled.Margin = new System.Windows.Forms.Padding(0);
+ cbEnabled.MouseLocation = new System.Drawing.Point(-1, -1);
+ cbEnabled.MouseState = MaterialSkin.MouseState.HOVER;
+ cbEnabled.Name = "cbEnabled";
+ cbEnabled.Ripple = true;
+ cbEnabled.Size = new System.Drawing.Size(58, 37);
+ cbEnabled.TabIndex = 8;
+ toolTip.SetToolTip(cbEnabled, "Enable the profile.");
+ cbEnabled.UseVisualStyleBackColor = true;
+ cbEnabled.CheckedChanged += CbEnabled_CheckedChanged;
+ //
+ // pnlMore
+ //
+ pnlMore.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ pnlMore.Controls.Add(btnDelete);
+ pnlMore.Controls.Add(splitter8);
+ pnlMore.Controls.Add(cbHandled);
+ pnlMore.Controls.Add(splitter7);
+ pnlMore.Controls.Add(sDelay);
+ pnlMore.Controls.Add(splitter6);
+ pnlMore.Controls.Add(txtPath);
+ pnlMore.Location = new System.Drawing.Point(0, 45);
+ pnlMore.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ pnlMore.Name = "pnlMore";
+ pnlMore.Padding = new System.Windows.Forms.Padding(72, 0, 0, 0);
+ pnlMore.Size = new System.Drawing.Size(725, 37);
+ pnlMore.TabIndex = 8;
+ //
+ // btnDelete
+ //
+ btnDelete.AutoSize = false;
+ btnDelete.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnDelete.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnDelete.Depth = 0;
+ btnDelete.Dock = System.Windows.Forms.DockStyle.Left;
+ btnDelete.HighEmphasis = true;
+ btnDelete.Icon = null;
+ btnDelete.Location = new System.Drawing.Point(650, 0);
+ btnDelete.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6);
+ btnDelete.MouseState = MaterialSkin.MouseState.HOVER;
+ btnDelete.Name = "btnDelete";
+ btnDelete.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnDelete.Size = new System.Drawing.Size(63, 37);
+ btnDelete.TabIndex = 21;
+ btnDelete.Text = "Delete";
+ toolTip.SetToolTip(btnDelete, "Delete current profile.");
+ btnDelete.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Text;
+ btnDelete.UseAccentColor = true;
+ btnDelete.UseVisualStyleBackColor = true;
+ btnDelete.Click += BtnDelete_Click;
+ //
+ // splitter8
+ //
+ splitter8.Location = new System.Drawing.Point(646, 0);
+ splitter8.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter8.MinSize = 80;
+ splitter8.Name = "splitter8";
+ splitter8.Size = new System.Drawing.Size(4, 37);
+ splitter8.TabIndex = 22;
+ splitter8.TabStop = false;
+ //
+ // cbHandled
+ //
+ cbHandled.AutoSize = true;
+ cbHandled.Checked = true;
+ cbHandled.CheckState = System.Windows.Forms.CheckState.Checked;
+ cbHandled.Depth = 0;
+ cbHandled.Dock = System.Windows.Forms.DockStyle.Left;
+ cbHandled.Location = new System.Drawing.Point(529, 0);
+ cbHandled.Margin = new System.Windows.Forms.Padding(0);
+ cbHandled.MouseLocation = new System.Drawing.Point(-1, -1);
+ cbHandled.MouseState = MaterialSkin.MouseState.HOVER;
+ cbHandled.Name = "cbHandled";
+ cbHandled.Ripple = true;
+ cbHandled.Size = new System.Drawing.Size(117, 37);
+ cbHandled.TabIndex = 7;
+ cbHandled.Text = "Handled";
+ toolTip.SetToolTip(cbHandled, "Prevent further processing of the hotkeys in other applications.");
+ cbHandled.UseVisualStyleBackColor = true;
+ cbHandled.CheckedChanged += CbHandled_CheckedChanged;
+ //
+ // splitter7
+ //
+ splitter7.Location = new System.Drawing.Point(525, 0);
+ splitter7.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter7.MinSize = 100;
+ splitter7.Name = "splitter7";
+ splitter7.Size = new System.Drawing.Size(4, 37);
+ splitter7.TabIndex = 19;
+ splitter7.TabStop = false;
+ //
+ // sDelay
+ //
+ sDelay.Depth = 0;
+ sDelay.Dock = System.Windows.Forms.DockStyle.Left;
+ sDelay.Font = new System.Drawing.Font("Roboto", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+ sDelay.FontType = MaterialSkin.MaterialSkinManager.fontType.Caption;
+ sDelay.ForeColor = System.Drawing.Color.FromArgb(222, 0, 0, 0);
+ sDelay.Location = new System.Drawing.Point(390, 0);
+ sDelay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ sDelay.MouseState = MaterialSkin.MouseState.HOVER;
+ sDelay.Name = "sDelay";
+ sDelay.RangeMax = 10000;
+ sDelay.ShowText = false;
+ sDelay.Size = new System.Drawing.Size(135, 40);
+ sDelay.TabIndex = 6;
+ sDelay.Text = "Delay";
+ toolTip.SetToolTip(sDelay, "Delay before doing the action.");
+ sDelay.Value = 0;
+ sDelay.ValueSuffix = " MS";
+ sDelay.onValueChanged += SDelay_ValueChanged;
+ //
+ // splitter6
+ //
+ splitter6.Location = new System.Drawing.Point(386, 0);
+ splitter6.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ splitter6.MinExtra = 450;
+ splitter6.MinSize = 155;
+ splitter6.Name = "splitter6";
+ splitter6.Size = new System.Drawing.Size(4, 37);
+ splitter6.TabIndex = 20;
+ splitter6.TabStop = false;
+ //
+ // txtPath
+ //
+ txtPath.AnimateReadOnly = false;
+ txtPath.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ txtPath.Depth = 0;
+ txtPath.Dock = System.Windows.Forms.DockStyle.Left;
+ txtPath.Font = new System.Drawing.Font("Roboto", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+ txtPath.Hint = "Path";
+ txtPath.LeadingIcon = null;
+ txtPath.LeaveOnEnterKey = true;
+ txtPath.Location = new System.Drawing.Point(72, 0);
+ txtPath.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ txtPath.MaxLength = 50;
+ txtPath.MouseState = MaterialSkin.MouseState.OUT;
+ txtPath.Multiline = false;
+ txtPath.Name = "txtPath";
+ txtPath.Size = new System.Drawing.Size(314, 36);
+ txtPath.TabIndex = 5;
+ txtPath.Text = "";
+ toolTip.SetToolTip(txtPath, "Folder path to open.");
+ txtPath.TrailingIcon = null;
+ txtPath.UseTallSize = false;
+ txtPath.TextChanged += TxtPath_TextChanged;
+ //
+ // HotKeyProfileControl
+ //
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
+ Controls.Add(pnlMore);
+ Controls.Add(pnlMain);
+ Margin = new System.Windows.Forms.Padding(3, 3, 3, 10);
+ Name = "HotKeyProfileControl";
+ Size = new System.Drawing.Size(725, 37);
+ pnlMain.ResumeLayout(false);
+ pnlMain.PerformLayout();
+ pnlMore.ResumeLayout(false);
+ pnlMore.PerformLayout();
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Panel pnlMain;
+ private MaterialSkin.Controls.MaterialSwitch cbEnabled;
+ private System.Windows.Forms.Splitter splitter1;
+ private MaterialSkin.Controls.MaterialTextBox txtName;
+ private MaterialSkin.Controls.MaterialTextBox txtHotKeys;
+ private System.Windows.Forms.Splitter splitter2;
+ private System.Windows.Forms.Splitter splitter3;
+ private MaterialSkin.Controls.MaterialComboBox cbScope;
+ private MaterialSkin.Controls.MaterialComboBox cbAction;
+ private System.Windows.Forms.Splitter splitter4;
+ private System.Windows.Forms.Splitter splitter5;
+ private MaterialSkin.Controls.MaterialButton btnCollapse;
+ private System.Windows.Forms.Panel pnlMore;
+ private System.Windows.Forms.Splitter splitter6;
+ private MaterialSkin.Controls.MaterialTextBox txtPath;
+ private MaterialSkin.Controls.MaterialSlider sDelay;
+ private MaterialSkin.Controls.MaterialSwitch cbHandled;
+ private System.Windows.Forms.Splitter splitter7;
+ private System.Windows.Forms.ToolTip toolTip;
+ private MaterialSkin.Controls.MaterialButton btnDelete;
+ private System.Windows.Forms.Splitter splitter8;
+}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/HotKeyProfileControl.cs b/ExplorerTabUtility/Forms/HotKeyProfileControl.cs
new file mode 100644
index 0000000..f09bf9a
--- /dev/null
+++ b/ExplorerTabUtility/Forms/HotKeyProfileControl.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Linq;
+using System.Windows.Forms;
+using ExplorerTabUtility.Models;
+using H.Hooks;
+
+namespace ExplorerTabUtility.Forms;
+
+public partial class HotKeyProfileControl : UserControl
+{
+ // Fields
+ private bool _isCollapsed;
+ private readonly int _collapsedHeight;
+ private readonly HotKeyProfile _profile;
+ private readonly Action? _removeAction;
+ private readonly Action? _keyboardHookStarted;
+ private readonly Action? _keyboardHookStopped;
+ private LowLevelKeyboardHook? _lowLevelKeyboardHook;
+ // Constants
+ private const int ExpandedHeight = 76;
+ // Properties
+ public bool IsEnabled
+ {
+ get => cbEnabled.Checked;
+ set
+ {
+ if (cbEnabled.Checked == value) return;
+ cbEnabled.Checked = value;
+ }
+ }
+ public bool IsCollapsed
+ {
+ get => _isCollapsed;
+ set
+ {
+ if (_isCollapsed == value) return;
+ _isCollapsed = value;
+ ToggleCollapse();
+ }
+ }
+
+ // Constructor
+ public HotKeyProfileControl(HotKeyProfile profile, Action? removeAction = default, Action? keyboardHookStarted = default, Action? keyboardHookStopped = default)
+ {
+ InitializeComponent();
+ Tag = profile;
+ _profile = profile;
+ _collapsedHeight = Height;
+ _removeAction = removeAction;
+ _keyboardHookStarted = keyboardHookStarted;
+ _keyboardHookStopped = keyboardHookStopped;
+ InitializeControls();
+ }
+ // Initialize controls with hot key profile data
+ private void InitializeControls()
+ {
+ cbEnabled.Checked = _profile.IsEnabled;
+ txtName.Text = _profile.Name ?? string.Empty;
+
+ if (_profile.HotKeys != null)
+ txtHotKeys.Text = string.Join(" + ", _profile.HotKeys.Select(k => k.ToFixedString()));
+
+ SetComboBoxDataSourceQuietly(cbScope, Enum.GetValues(typeof(HotkeyScope)), CbScope_SelectedIndexChanged);
+ SetComboBoxDataSourceQuietly(cbAction, Enum.GetValues(typeof(HotKeyAction)), CbAction_SelectedIndexChanged);
+ cbScope.SelectedItem = _profile.Scope;
+ cbAction.SelectedItem = _profile.Action;
+ txtPath.Text = _profile.Path ?? string.Empty;
+ sDelay.Value = _profile.Delay;
+ cbHandled.Checked = _profile.IsHandled;
+ }
+
+ // Event handlers
+ private void CbEnabled_CheckedChanged(object? _, EventArgs __) => UpdateControlsEnabledState();
+ private void TxtName_TextChanged(object? _, EventArgs __) => _profile.Name = txtName.Text;
+ private void CbScope_SelectedIndexChanged(object? _, EventArgs __) => _profile.Scope = (HotkeyScope)cbScope.SelectedItem;
+ private void CbAction_SelectedIndexChanged(object? _, EventArgs __) => UpdateAction();
+ private void TxtPath_TextChanged(object? _, EventArgs __) => _profile.Path = txtPath.Text;
+ private void CbHandled_CheckedChanged(object? _, EventArgs __) => _profile.IsHandled = cbHandled.Checked;
+ private void SDelay_ValueChanged(object? _, int newValue) => _profile.Delay = newValue;
+ private void BtnCollapse_Click(object? _, EventArgs __) => IsCollapsed = !_isCollapsed;
+ private void BtnDelete_Click(object? _, EventArgs __) => _removeAction?.Invoke(_profile);
+ private void TxtHotKeys_KeyDown(object? _, KeyEventArgs e) => e.SuppressKeyPress = true;
+ private void TxtHotKeys_Enter(object? _, EventArgs __) => InitializeKeyboardHook();
+ private void TxtHotKeys_Leave(object? _, EventArgs __)
+ {
+ DisposeKeyboardHook();
+
+ // If the name is empty, set it to the hotkey.
+ if (string.IsNullOrEmpty(txtName.Text))
+ txtName.Text = txtHotKeys.Text;
+ }
+ private void LowLevelKeyboardHook_Down(object? _, KeyboardEventArgs e)
+ {
+ // Backspace removes the hotkey.
+ if (e.Keys.Are(Key.Back))
+ {
+ Invoke(() => txtHotKeys.Text = string.Empty);
+ _profile.HotKeys = null;
+ return;
+ }
+
+ // Two or more keys and must have a modifier key. (CTRL, ALT, SHIFT, WIN)
+ if (e.Keys.Values.Count < 2 || !e.Keys.Values.Any(k => k is Key.Ctrl or Key.Alt or Key.Shift or Key.LWin or Key.RWin))
+ return;
+
+ // Prevent the key from being handled by other applications.
+ e.IsHandled = true;
+
+ // Order the keys by modifier keys first, then by key.
+ var keys = e.Keys.Values
+ .OrderByDescending(key => key is Key.LWin or Key.RWin)
+ .ThenBy(key => key)
+ .ToArray();
+
+ _profile.HotKeys = keys;
+ Invoke(() => txtHotKeys.Text = string.Join(" + ", keys.Select(k => k.ToFixedString())));
+ }
+
+ // Methods
+ private static void SetComboBoxDataSourceQuietly(ComboBox comboBox, object datasource, EventHandler eventHandler)
+ {
+ comboBox.SelectedIndexChanged -= eventHandler;
+ comboBox.DataSource = datasource;
+ comboBox.SelectedIndexChanged += eventHandler;
+ }
+ private void ToggleCollapse()
+ {
+ switch (_isCollapsed)
+ {
+ case true:
+ Height = ExpandedHeight;
+ btnCollapse.Text = @"ᐱ";
+ break;
+ default:
+ Height = _collapsedHeight;
+ btnCollapse.Text = @"ᐯ";
+ break;
+ }
+ }
+ private void UpdateControlsEnabledState()
+ {
+ var isEnabled = cbEnabled.Checked;
+ _profile.IsEnabled = isEnabled;
+
+ // Disable all controls if cbEnabled is unchecked, except for cbEnabled and btnCollapse.
+ txtName.Enabled = isEnabled;
+ txtHotKeys.Enabled = isEnabled;
+ cbScope.Enabled = isEnabled;
+ cbAction.Enabled = isEnabled;
+ txtPath.Enabled = isEnabled;
+ sDelay.Enabled = isEnabled;
+ cbHandled.Enabled = isEnabled;
+ }
+ private void UpdateAction()
+ {
+ var selectedAction = (HotKeyAction)cbAction.SelectedItem;
+ _profile.Action = selectedAction;
+
+ switch (selectedAction)
+ {
+ case HotKeyAction.Open:
+ {
+ txtPath.Enabled = true;
+ txtPath.Text = _profile.Path ?? string.Empty;
+ break;
+ }
+ case HotKeyAction.Duplicate:
+ {
+ txtPath.Enabled = false;
+ cbScope.SelectedIndex = cbScope.FindStringExact(nameof(HotkeyScope.FileExplorer));
+ cbScope.Invalidate();
+ break;
+ }
+ }
+ }
+ private void InitializeKeyboardHook()
+ {
+ DisposeKeyboardHook(false);
+ _keyboardHookStarted?.Invoke();
+ _lowLevelKeyboardHook = new LowLevelKeyboardHook { Handling = true };
+ _lowLevelKeyboardHook.Down += LowLevelKeyboardHook_Down;
+ _lowLevelKeyboardHook.Start();
+ }
+ private void DisposeKeyboardHook(bool inform = true)
+ {
+ if (_lowLevelKeyboardHook == default) return;
+ _lowLevelKeyboardHook.Stop();
+ _lowLevelKeyboardHook.Dispose();
+
+ if (inform)
+ _keyboardHookStopped?.Invoke();
+ }
+}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/HotKeyProfileControl.resx b/ExplorerTabUtility/Forms/HotKeyProfileControl.resx
new file mode 100644
index 0000000..1f052d5
--- /dev/null
+++ b/ExplorerTabUtility/Forms/HotKeyProfileControl.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ 17, 17
+
+
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/MainForm.Designer.cs b/ExplorerTabUtility/Forms/MainForm.Designer.cs
new file mode 100644
index 0000000..63549e1
--- /dev/null
+++ b/ExplorerTabUtility/Forms/MainForm.Designer.cs
@@ -0,0 +1,211 @@
+using ExplorerTabUtility.Models;
+
+namespace ExplorerTabUtility.Forms
+{
+ partial class MainForm
+ {
+ ///
+ /// 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()
+ {
+ components = new System.ComponentModel.Container();
+ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
+ label1 = new System.Windows.Forms.Label();
+ flpProfiles = new System.Windows.Forms.FlowLayoutPanel();
+ btnNewProfile = new MaterialSkin.Controls.MaterialButton();
+ btnImport = new MaterialSkin.Controls.MaterialButton();
+ btnExport = new MaterialSkin.Controls.MaterialButton();
+ btnSave = new MaterialSkin.Controls.MaterialButton();
+ cbSaveProfilesOnExit = new MaterialSkin.Controls.MaterialCheckbox();
+ toolTip = new System.Windows.Forms.ToolTip(components);
+ SuspendLayout();
+ //
+ // label1
+ //
+ label1.AutoSize = true;
+ label1.BackColor = System.Drawing.Color.Transparent;
+ label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Pixel);
+ label1.ForeColor = System.Drawing.SystemColors.ControlLightLight;
+ label1.Location = new System.Drawing.Point(4, 2);
+ label1.Name = "label1";
+ label1.Size = new System.Drawing.Size(127, 17);
+ label1.TabIndex = 0;
+ label1.Text = "Explorer Tab Utility";
+ //
+ // flpProfiles
+ //
+ flpProfiles.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ flpProfiles.AutoScroll = true;
+ flpProfiles.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
+ flpProfiles.Location = new System.Drawing.Point(7, 112);
+ flpProfiles.Name = "flpProfiles";
+ flpProfiles.Size = new System.Drawing.Size(748, 283);
+ flpProfiles.TabIndex = 2;
+ flpProfiles.WrapContents = false;
+ flpProfiles.Resize += FlpProfiles_Resize;
+ //
+ // btnNewProfile
+ //
+ btnNewProfile.AutoSize = false;
+ btnNewProfile.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnNewProfile.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnNewProfile.Depth = 0;
+ btnNewProfile.HighEmphasis = true;
+ btnNewProfile.Icon = null;
+ btnNewProfile.Location = new System.Drawing.Point(12, 68);
+ btnNewProfile.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6);
+ btnNewProfile.MouseState = MaterialSkin.MouseState.HOVER;
+ btnNewProfile.Name = "btnNewProfile";
+ btnNewProfile.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnNewProfile.Size = new System.Drawing.Size(76, 36);
+ btnNewProfile.TabIndex = 5;
+ btnNewProfile.Text = "New";
+ toolTip.SetToolTip(btnNewProfile, "New profile.");
+ btnNewProfile.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Text;
+ btnNewProfile.UseAccentColor = true;
+ btnNewProfile.UseVisualStyleBackColor = true;
+ btnNewProfile.Click += BtnNewProfile_Click;
+ //
+ // btnImport
+ //
+ btnImport.AutoSize = false;
+ btnImport.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnImport.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnImport.Depth = 0;
+ btnImport.HighEmphasis = true;
+ btnImport.Icon = null;
+ btnImport.Location = new System.Drawing.Point(94, 68);
+ btnImport.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6);
+ btnImport.MouseState = MaterialSkin.MouseState.HOVER;
+ btnImport.Name = "btnImport";
+ btnImport.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnImport.Size = new System.Drawing.Size(76, 36);
+ btnImport.TabIndex = 6;
+ btnImport.Text = "Import";
+ toolTip.SetToolTip(btnImport, "Import profiles.");
+ btnImport.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Text;
+ btnImport.UseAccentColor = true;
+ btnImport.UseVisualStyleBackColor = true;
+ btnImport.Click += BtnImport_Click;
+ //
+ // btnExport
+ //
+ btnExport.AutoSize = false;
+ btnExport.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnExport.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnExport.Depth = 0;
+ btnExport.HighEmphasis = true;
+ btnExport.Icon = null;
+ btnExport.Location = new System.Drawing.Point(176, 68);
+ btnExport.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6);
+ btnExport.MouseState = MaterialSkin.MouseState.HOVER;
+ btnExport.Name = "btnExport";
+ btnExport.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnExport.Size = new System.Drawing.Size(76, 36);
+ btnExport.TabIndex = 7;
+ btnExport.Text = "Export";
+ toolTip.SetToolTip(btnExport, "Export profiles.");
+ btnExport.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Text;
+ btnExport.UseAccentColor = true;
+ btnExport.UseVisualStyleBackColor = true;
+ btnExport.Click += BtnExport_Click;
+ //
+ // btnSave
+ //
+ btnSave.AutoSize = false;
+ btnSave.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
+ btnSave.Density = MaterialSkin.Controls.MaterialButton.MaterialButtonDensity.Default;
+ btnSave.Depth = 0;
+ btnSave.HighEmphasis = true;
+ btnSave.Icon = null;
+ btnSave.Location = new System.Drawing.Point(258, 68);
+ btnSave.Margin = new System.Windows.Forms.Padding(4, 6, 4, 6);
+ btnSave.MouseState = MaterialSkin.MouseState.HOVER;
+ btnSave.Name = "btnSave";
+ btnSave.NoAccentTextColor = System.Drawing.Color.Empty;
+ btnSave.Size = new System.Drawing.Size(76, 36);
+ btnSave.TabIndex = 8;
+ btnSave.Text = "Save";
+ toolTip.SetToolTip(btnSave, "Persist profiles for next time you open the app.");
+ btnSave.Type = MaterialSkin.Controls.MaterialButton.MaterialButtonType.Text;
+ btnSave.UseAccentColor = true;
+ btnSave.UseVisualStyleBackColor = true;
+ btnSave.Click += BtnSave_Click;
+ //
+ // cbSaveProfilesOnExit
+ //
+ cbSaveProfilesOnExit.AutoSize = true;
+ cbSaveProfilesOnExit.Checked = true;
+ cbSaveProfilesOnExit.CheckState = System.Windows.Forms.CheckState.Checked;
+ cbSaveProfilesOnExit.Depth = 0;
+ cbSaveProfilesOnExit.Location = new System.Drawing.Point(610, 68);
+ cbSaveProfilesOnExit.Margin = new System.Windows.Forms.Padding(0);
+ cbSaveProfilesOnExit.MouseLocation = new System.Drawing.Point(-1, -1);
+ cbSaveProfilesOnExit.MouseState = MaterialSkin.MouseState.HOVER;
+ cbSaveProfilesOnExit.Name = "cbSaveProfilesOnExit";
+ cbSaveProfilesOnExit.ReadOnly = false;
+ cbSaveProfilesOnExit.Ripple = true;
+ cbSaveProfilesOnExit.Size = new System.Drawing.Size(121, 37);
+ cbSaveProfilesOnExit.TabIndex = 9;
+ cbSaveProfilesOnExit.Text = "Save on exit";
+ toolTip.SetToolTip(cbSaveProfilesOnExit, "Automatically saves your profiles on exit.\r\nTo persist profiles for next time you open the app.");
+ cbSaveProfilesOnExit.UseVisualStyleBackColor = true;
+ cbSaveProfilesOnExit.CheckedChanged += CbSaveProfilesOnExit_CheckedChanged;
+ //
+ // MainForm
+ //
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
+ ClientSize = new System.Drawing.Size(762, 402);
+ Controls.Add(cbSaveProfilesOnExit);
+ Controls.Add(btnSave);
+ Controls.Add(btnExport);
+ Controls.Add(btnImport);
+ Controls.Add(btnNewProfile);
+ Controls.Add(flpProfiles);
+ Controls.Add(label1);
+ Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon");
+ MinimumSize = new System.Drawing.Size(762, 225);
+ Name = "MainForm";
+ Padding = new System.Windows.Forms.Padding(3, 55, 3, 3);
+ Text = "Settings";
+ Deactivate += MainForm_Deactivate;
+ FormClosing += MainForm_FormClosing;
+ Resize += MainForm_Resize;
+ ResumeLayout(false);
+ PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.FlowLayoutPanel flpProfiles;
+ private MaterialSkin.Controls.MaterialButton btnNewProfile;
+ private MaterialSkin.Controls.MaterialButton btnImport;
+ private MaterialSkin.Controls.MaterialButton btnExport;
+ private MaterialSkin.Controls.MaterialButton btnSave;
+ private MaterialSkin.Controls.MaterialCheckbox cbSaveProfilesOnExit;
+ private System.Windows.Forms.ToolTip toolTip;
+ }
+}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/MainForm.cs b/ExplorerTabUtility/Forms/MainForm.cs
new file mode 100644
index 0000000..c9561b6
--- /dev/null
+++ b/ExplorerTabUtility/Forms/MainForm.cs
@@ -0,0 +1,374 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Windows.Forms;
+using System.Collections.Generic;
+using System.Diagnostics;
+using MaterialSkin;
+using MaterialSkin.Controls;
+using ExplorerTabUtility.Models;
+using ExplorerTabUtility.WinAPI;
+using ExplorerTabUtility.Helpers;
+using ExplorerTabUtility.Managers;
+
+namespace ExplorerTabUtility.Forms;
+
+public partial class MainForm : MaterialForm
+{
+ private readonly List _hotKeyProfiles = [];
+ private bool _allowVisible;
+ private NotifyIcon _notifyIcon = null!;
+ private readonly HookManager _hookManager;
+
+ public MainForm()
+ {
+ Application.ApplicationExit += OnApplicationExit;
+ InteractionManager.InteractionMethod = (InteractionMethod)SettingsManager.InteractionMethod;
+ _hookManager = new HookManager(_hotKeyProfiles, InteractionManager.OnHotKeyProfileTriggered, InteractionManager.OnNewWindow);
+
+ InitializeComponent();
+ SetupMaterialSkin();
+ InitializeNotifyIcon();
+ StartHooks();
+ }
+
+ private void SetupMaterialSkin()
+ {
+ var materialSkinManager = MaterialSkinManager.Instance;
+ materialSkinManager.EnforceBackcolorOnAllComponents = false;
+ materialSkinManager.AddFormToManage(this);
+ materialSkinManager.Theme = MaterialSkinManager.Themes.DARK;
+ materialSkinManager.ColorScheme = new ColorScheme(Primary.Blue800, Primary.Blue900, Primary.LightBlue600, Accent.LightBlue200, TextShade.WHITE);
+ }
+ private void InitializeNotifyIcon()
+ {
+ var importedList = DeserializeHotKeyProfiles(SettingsManager.HotKeyProfiles);
+ if (importedList != default) AddProfiles(importedList);
+
+ _notifyIcon = new NotifyIcon
+ {
+ Icon = Helper.GetIcon(),
+ Text = Constants.NotifyIconText,
+
+ ContextMenuStrip = CreateContextMenuStrip(),
+ Visible = true
+ };
+
+ // Show the form when the user double-clicks on the notify icon.
+ _notifyIcon.MouseDoubleClick += (_, e) =>
+ {
+ if (e.Button != MouseButtons.Left) return;
+ ShowForm();
+ };
+ }
+ private void StartHooks()
+ {
+ if (SettingsManager.IsWindowHookActive) _hookManager.StartWindowHook();
+ if (SettingsManager.IsKeyboardHookActive) _hookManager.StartKeyboardHook();
+ }
+
+ private ContextMenuStrip CreateContextMenuStrip()
+ {
+ var strip = new ContextMenuStrip();
+
+ // Interaction Menu
+ strip.Items.Add(CreateInteractionMethodMenuItem());
+
+ // KeyboardHook Menu
+ strip.Items.Add(CreateKeyboardHookMenuItem());
+
+ // WindowHook
+ strip.Items.Add(CreateMenuItem("Window Hook", SettingsManager.IsWindowHookActive, ToggleWindowHook));
+
+ // Separator
+ strip.Items.Add(new ToolStripSeparator());
+
+ // Startup
+ strip.Items.Add(CreateMenuItem("Add to startup", RegistryManager.IsInStartup(), static (_, _) => RegistryManager.ToggleStartup()));
+
+ // Settings
+ strip.Items.Add(CreateMenuItem("Settings", false, (_, _) => ShowForm(), checkOnClick: false));
+
+ // Separator
+ strip.Items.Add(new ToolStripSeparator());
+
+ // Exit
+ strip.Items.Add(CreateMenuItem("Exit", false, static (_, _) => Application.Exit()));
+
+ return strip;
+ }
+ private ToolStripMenuItem CreateKeyboardHookMenuItem()
+ {
+ var menuItem = CreateMenuItem("Keyboard Hook", SettingsManager.IsKeyboardHookActive, ToggleKeyboardHook, "KeyboardHookMenu");
+
+ AddProfilesToMenuItem(menuItem);
+ return menuItem;
+ }
+ private void UpdateKeyboardHookMenu()
+ {
+ var menuItem = (ToolStripMenuItem?)_notifyIcon.ContextMenuStrip?.Items["KeyboardHookMenu"];
+ if (menuItem == default) return;
+
+ menuItem.DropDownItems.Clear();
+ AddProfilesToMenuItem(menuItem);
+ }
+ private void AddProfilesToMenuItem(ToolStripMenuItem menuItem)
+ {
+ foreach (var profile in _hotKeyProfiles)
+ {
+ var profileMenuItem = CreateMenuItem(profile.Name ?? string.Empty, profile.IsEnabled, eventHandler: KeyboardHookProfileItemClick);
+ profileMenuItem.Tag = profile;
+ menuItem.DropDownItems.Add(profileMenuItem);
+ }
+ }
+ private ToolStripMenuItem CreateInteractionMethodMenuItem()
+ {
+ var isUiAutomation = InteractionManager.InteractionMethod == InteractionMethod.UiAutomation;
+
+ var windowHookMenuItem = CreateMenuItem("Interaction Method", checkOnClick: false,
+ dropDownItems:
+ [
+ CreateMenuItem("UIAutomation (Recommended)", isUiAutomation, InteractionMethodChanged, nameof(InteractionMethod.UiAutomation), false),
+ CreateMenuItem("Keyboard", !isUiAutomation, InteractionMethodChanged, nameof(InteractionMethod.Keyboard), false)
+ ]);
+
+ return windowHookMenuItem;
+ }
+ private static ToolStripMenuItem CreateMenuItem(string text, bool isChecked = default, EventHandler? eventHandler = default,
+ string? name = default, bool checkOnClick = true, params ToolStripItem[] dropDownItems)
+ {
+ var item = new ToolStripMenuItem
+ {
+ Text = text,
+ Checked = isChecked,
+ CheckOnClick = checkOnClick
+ };
+
+ if (name != default)
+ item.Name = name;
+
+ if (eventHandler != default)
+ item.Click += eventHandler;
+
+ if (dropDownItems.Length > 0)
+ item.DropDownItems.AddRange(dropDownItems);
+
+ return item;
+ }
+
+ private void UpdateMenuAndSaveProfiles()
+ {
+ // Remove profiles that don't have any hotkeys.
+ _hotKeyProfiles.FindAll(p => p.HotKeys?.Any() != true).ForEach(RemoveProfile);
+
+ UpdateKeyboardHookMenu();
+
+ SettingsManager.HotKeyProfiles = JsonSerializer.Serialize(_hotKeyProfiles);
+ }
+ private void AddProfiles(List profiles, bool clear = false)
+ {
+ flpProfiles.SuspendLayout();
+ flpProfiles.SuspendDrawing();
+
+ if (clear) ClearProfiles();
+
+ profiles.ForEach(AddProfile);
+
+ flpProfiles.ResumeDrawing();
+ flpProfiles.ResumeLayout();
+ }
+ private void AddProfile(HotKeyProfile? profile = default)
+ {
+ _hotKeyProfiles.Add(profile ??= new HotKeyProfile());
+ flpProfiles.Controls.Add(new HotKeyProfileControl(profile, RemoveProfile, ControlStartedKeyboardHook, ControlStoppedKeyboardHook));
+ }
+ private void ClearProfiles()
+ {
+ _hotKeyProfiles.Clear();
+ flpProfiles.Controls.Clear();
+ }
+ private void RemoveProfile(HotKeyProfile profile)
+ {
+ _hotKeyProfiles.Remove(profile);
+
+ var control = FindControlByProfile(profile);
+ if (control != default)
+ flpProfiles.Controls.Remove(control);
+ }
+ private HotKeyProfileControl? FindControlByProfile(HotKeyProfile profile)
+ {
+ return flpProfiles.Controls
+ .OfType()
+ .FirstOrDefault(c => c.Tag?.Equals(profile) == true);
+ }
+ private static List? DeserializeHotKeyProfiles(string jsonString)
+ {
+ try
+ {
+ return JsonSerializer.Deserialize>(jsonString);
+ }
+ catch
+ {
+ return default;
+ }
+ }
+
+ private void ControlStartedKeyboardHook()
+ {
+ Debug.WriteLine($"{nameof(ControlStartedKeyboardHook)}");
+ if (!SettingsManager.IsKeyboardHookActive) return;
+ _hookManager.StopKeyboardHook();
+ }
+ private void ControlStoppedKeyboardHook()
+ {
+ Debug.WriteLine($"{nameof(ControlStoppedKeyboardHook)}");
+ if (!SettingsManager.IsKeyboardHookActive) return;
+ _hookManager.StartKeyboardHook();
+ }
+ private static void InteractionMethodChanged(object? sender, EventArgs _)
+ {
+ if (sender is not ToolStripMenuItem item) return;
+ if (!Enum.TryParse(item.Name, out InteractionMethod method)) return;
+ InteractionManager.InteractionMethod = method;
+
+ foreach (ToolStripMenuItem radio in item.GetCurrentParent().Items)
+ radio.Checked = radio == item;
+
+ SettingsManager.InteractionMethod = (int)method;
+ }
+ private void KeyboardHookProfileItemClick(object? sender, EventArgs _)
+ {
+ if (sender is not ToolStripMenuItem item || item.OwnerItem is not ToolStripMenuItem parent) return;
+
+ // Toggle the HotKeyProfileControl's Enabled state.
+ if (item.Tag is HotKeyProfile profile)
+ {
+ var control = FindControlByProfile(profile);
+ if (control != null) control.IsEnabled = item.Checked;
+ }
+
+ // Uncheck parent if all sub items are unchecked.
+ parent.Checked = parent.DropDownItems.OfType().Any(c => c.Checked);
+ if (!parent.Checked)
+ ToggleKeyboardHook(parent, EventArgs.Empty);
+ }
+ private void ToggleKeyboardHook(object? sender, EventArgs _)
+ {
+ if (sender is not ToolStripMenuItem item) return;
+
+ SettingsManager.IsKeyboardHookActive = item.Checked;
+
+ foreach (ToolStripItem subItem in item.DropDownItems)
+ subItem.Enabled = item.Checked;
+
+ if (item.Checked)
+ {
+ // If all sub items are not checked, click the first item.
+ if (_hotKeyProfiles.TrueForAll(h => !h.IsEnabled))
+ item.DropDownItems[0].PerformClick();
+
+ _hookManager.StartKeyboardHook();
+ }
+ else
+ _hookManager.StopKeyboardHook();
+ }
+ private void ToggleWindowHook(object? sender, EventArgs _)
+ {
+ if (sender is not ToolStripMenuItem item) return;
+
+ SettingsManager.IsWindowHookActive = item.Checked;
+
+ if (item.Checked)
+ _hookManager.StartWindowHook();
+ else
+ _hookManager.StopWindowHook();
+ }
+ private void BtnNewProfile_Click(object _, EventArgs __) => AddProfile();
+ private void BtnImport_Click(object _, EventArgs __)
+ {
+ using var ofd = new OpenFileDialog();
+ ofd.FileName = Constants.HotKeyProfilesFileName;
+ ofd.Filter = Constants.JsonFileFilter;
+ if (ofd.ShowDialog() != DialogResult.OK) return;
+
+ var jsonString = System.IO.File.ReadAllText(ofd.FileName);
+ var importedList = DeserializeHotKeyProfiles(jsonString);
+ if (importedList == default) return;
+
+ AddProfiles(importedList, true);
+ }
+ private void BtnExport_Click(object _, EventArgs __)
+ {
+ using var sfd = new SaveFileDialog();
+ sfd.FileName = Constants.HotKeyProfilesFileName;
+ sfd.Filter = Constants.JsonFileFilter;
+ if (sfd.ShowDialog() != DialogResult.OK) return;
+
+ using var openFile = sfd.OpenFile();
+ var bytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_hotKeyProfiles));
+ openFile.Write(bytes, 0, bytes.Length);
+ }
+ private void BtnSave_Click(object _, EventArgs __) => UpdateMenuAndSaveProfiles();
+ private void CbSaveProfilesOnExit_CheckedChanged(object _, EventArgs __) => SettingsManager.SaveProfilesOnExit = cbSaveProfilesOnExit.Checked;
+ private void FlpProfiles_Resize(object _, EventArgs __)
+ {
+ foreach (Control c in flpProfiles.Controls)
+ c.Width = flpProfiles.Width - 20;
+ }
+ private void MainForm_Resize(object _, EventArgs __)
+ {
+ if (WindowState == FormWindowState.Normal)
+ {
+ _allowVisible = true;
+ }
+ else if (WindowState == FormWindowState.Minimized)
+ {
+ WindowState = FormWindowState.Normal;
+ _allowVisible = false;
+ Hide();
+
+ if (!cbSaveProfilesOnExit.Checked) return;
+
+ // Save
+ UpdateMenuAndSaveProfiles();
+ }
+ }
+ private void MainForm_FormClosing(object _, FormClosingEventArgs e)
+ {
+ if (e.CloseReason != CloseReason.UserClosing) return;
+
+ e.Cancel = true;
+ _allowVisible = false;
+
+ // Hide the form instead of closing it when the close button is clicked
+ Hide();
+
+ if (!cbSaveProfilesOnExit.Checked) return;
+
+ // Save
+ UpdateMenuAndSaveProfiles();
+ }
+ private void MainForm_Deactivate(object _, EventArgs __) => flpProfiles.Focus();
+ private void OnApplicationExit(object? _, EventArgs __)
+ {
+ _notifyIcon.Visible = false;
+ _hookManager.Dispose();
+ }
+
+ private void ShowForm()
+ {
+ _allowVisible = true;
+ WindowState = FormWindowState.Normal;
+ Show();
+ }
+ protected override void SetVisibleCore(bool value)
+ {
+ if (!_allowVisible)
+ {
+ value = false;
+ if (!IsHandleCreated) CreateHandle();
+ }
+ base.SetVisibleCore(value);
+ }
+}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/MainForm.resx b/ExplorerTabUtility/Forms/MainForm.resx
new file mode 100644
index 0000000..0cb0265
--- /dev/null
+++ b/ExplorerTabUtility/Forms/MainForm.resx
@@ -0,0 +1,763 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ 17, 17
+
+
+
+
+ AAABAAEAYGAAAAEAIAColAAAFgAAACgAAABgAAAAwAAAAAEAIAAAAAAAAJAAAMMOAADDDgAAAAAAAAAA
+ AADpuAb/57oH/+e5B//ouAb/6LkG/+e5B//nuAj/6LgI/+m4B//ouQb/6LgH/+i4B//puAj/6bgH/+m5
+ B//puQf/6LkH/+a5B//puAj/6LcH/+m4Bv/puQb/57kG/+e5Bv/muQb/57gH/+a3B//muAb/6LkG/+m5
+ B//puQf/6bkH/+i4Bv/nuAj/57kI/+e5B//nuAj/57gH/+e5B//puAj/6LcG/+m5B//puQb/6bkG/+i4
+ Bv/puQf/6bkH/+m5B//nugf/6LkH/+m5B//puQb/6LkH/+a4B//otwf/57cH/+a4CP/nuAj/6LgI/+i3
+ B//nuAj/57kH/+e5Bv/nuAj/57gI/+a3B//muQf/6LkH/+m4Bv/puQf/6LgG/+i4Bv/puQf/6LkH/+e5
+ B//nuQf/6LkH/+m5B//puQf/6bkH/+m5Bv/puQf/6bkH/+m5B//puQf/6bkH/+m5B//nuQf/57kH/+e5
+ B//nuQf/6LkH/+m5B//pugX/6bkG/+m5B//quAf/6boI/+m6CP/qugj/6roJ/+m6Cf/puQr/6bkJ/+q5
+ Cf/quQn/6rgJ/+q4Cv/quQn/6rkJ/+q5Cf/qugj/6boI/+m6CP/puQn/6bkJ/+m6Cf/rugn/6boJ/+i6
+ Cf/pugj/6boJ/+i5Cf/ougj/6roJ/+q6Cf/qugn/6rkI/+m6Cf/ouQn/6LoJ/+m7Cf/pugr/6bkJ/+m5
+ CP/puQn/6rkJ/+q6Cf/quAn/6rkI/+q6Cf/pugn/6roJ/+q6Cf/pugr/6roJ/+u6Cf/quQr/6bkK/+i5
+ Cf/quQn/6bkJ/+i5Cf/puQn/6bkJ/+m5Cf/puQn/6LkJ/+i5Cv/ouQn/6boK/+i5Cf/ougn/6boJ/+m5
+ Cv/pugn/6roJ/+q5CP/qugj/6roI/+q7Cf/ougn/6boJ/+q6Cf/quwn/6roJ/+u6Cf/rugn/67oJ/+q6
+ CP/quwn/6roJ/+q6Cf/ougn/6boJ/+m6Cf/ougn/6rsJ/+u7Cf/qugj/6roJ/+q5Cf/quAf/6rkI/+q6
+ CP/pugj/6rkI/+q5Cf/quAr/6rgK/+q5Cf/puQj/6rkJ/+q5Cf/puAj/6bgJ/+q4Cv/quQj/6roI/+q6
+ CP/ouQn/6LkJ/+i5Cf/quwj/6rsJ/+m7CP/ruwn/6roJ/+i6Cf/ougj/6boJ/+q5Cf/quQn/6bkJ/+i6
+ Cv/ouQn/6LoJ/+i6CP/ouQn/6bkJ/+q5Cf/nuAn/6bkJ/+q5Cf/quQn/6rkJ/+q5Cf/ouQr/6LoK/+e5
+ Cf/pugn/6boK/+q5Cv/ouQn/6LoJ/+m7CP/quQn/6boJ/+m6Cv/ruQr/6boJ/+i5Cf/quQj/6bkJ/+i5
+ Cf/ouQn/6LkJ/+m6Cv/ouQn/6boK/+i6Cf/ougr/6boK/+q5Cf/qugj/6rsJ/+q6Cf/pugr/6LkJ/+i6
+ Cf/ougn/6boI/+u7Cf/quQn/6roJ/+q7CP/puwn/6roJ/+q5Cv/puQr/6bkK/+m6Cv/puwn/6rsJ/+q6
+ Cf/qugj/6roI/+q6CP/puQf/6boI/+i6CP/puQf/6rkI/+q5Cf/quAj/6bgI/+e4CP/qugj/6boI/+i6
+ CP/ouQn/6LkJ/+m5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LoJ/+i6CP/puwn/6boI/+q6CP/ougj/6LoI/+e5
+ B//ougj/6LoI/+i6CP/pugj/6boI/+m7Cf/pugr/6LkI/+e5B//nugf/6LoJ/+i5Cf/ouAn/6LkJ/+i5
+ Cf/ouQn/6LkI/+i6CP/ouQn/6boJ/+i6CP/ouQn/6LkJ/+i5Cf/ougj/6boJ/+i5Cf/ouQn/6LkI/+m7
+ CP/ougj/6LoI/+m7Cf/pugn/6LoI/+i6CP/ougj/6LoJ/+i5Cf/ouQn/6LoJ/+m7CP/puwn/6boJ/+m6
+ Cf/ougj/6LoI/+i6CP/ouQn/6boJ/+i6CP/pugr/6bkK/+i5Cf/pugj/6bsH/+m8Bv/puwn/6bsJ/+m7
+ Cf/ouQn/6LkJ/+m6Cv/ougj/6LoI/+m6CP/qugf/6rkI/+q4Cf/puAb/6boI/+i6CP/ougf/6bkI/+m5
+ Cf/puQn/6LgJ/+e4CP/ouQf/6LkI/+e5CP/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5
+ CP/ougj/6LoI/+m5CP/ouQj/6LkI/+i5CP/ougj/6LoJ/+i6Cf/ouQj/6LkI/+i5CP/pugr/6LkJ/+i5
+ CP/ougn/6LkJ/+i5Cf/nuQj/6LkJ/+i5Cf/ouQn/57kI/+i6CP/ouQn/6LoJ/+i6CP/ougn/6LoJ/+i6
+ Cf/ougj/6LoI/+i6Cf/pugr/6boJ/+m6Cf/ougn/6LoI/+m7Cf/puwn/6LoI/+i6CP/ougj/6boJ/+i5
+ Cf/ouQj/6LoJ/+m7CP/ougj/6LoI/+i6CP/ouwn/6LoI/+i6Cf/ouQj/6boJ/+i6Cf/ouQn/6boJ/+i6
+ Cf/pugn/6LoI/+i6B//ougn/6LoJ/+i6Cf/ouQn/6boJ/+i6Cf/ougj/57kI/+i5CP/ouQj/6bkI/+q5
+ CP/puQf/6boI/+e6CP/ougj/6LkI/+i5Cf/ouQr/57gJ/+e4CP/nuQf/6LkI/+i5Cf/ouQn/57gI/+i5
+ Cf/ouQn/6LkJ/+i5Cf/ouQn/6LgJ/+i5Cf/ougj/57kI/+i5Cf/ouQn/6LkJ/+i5Cf/puwn/6LoJ/+i5
+ Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ougj/6LkI/+i5Cf/ouQn/6LkI/+i6
+ CP/ouQn/6LoJ/+m7CP/puwj/6LoI/+i6CP/ougj/6LoI/+i7CP/pugn/6LkJ/+m6Cv/pugr/6bsK/+m7
+ Cf/ougj/6LoI/+i6CP/ougj/6LoJ/+m6Cv/ougj/6LoI/+m7Cf/puwn/6bsJ/+m7Cf/ougj/6LkJ/+i5
+ Cf/ougf/6boJ/+i5Cv/ougn/6LoJ/+m7Cf/pugr/6LkJ/+m6Cv/pugr/6boK/+i5Cf/ouQn/6boJ/+i6
+ CP/ougj/6LkI/+i5Cf/ouQn/6boJ/+q6B//puAj/6boJ/+e6CP/ouQn/6LkI/+i6CP/ouQn/57gI/+i5
+ Cf/ouQn/57gI/+e4CP/ouQn/57gI/+i5Cf/ouQn/6LkI/+e5CP/ouQn/6LkJ/+i5Cf/ougj/57oH/+i6
+ CP/ougj/6LkI/+i5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ouAn/6LkI/+i6CP/ouQn/6LkJ/+i5
+ Cf/ouQn/57kI/+i6CP/nuQf/6LoI/+i6CP/ouQn/6LoJ/+i6CP/pugr/6boJ/+i6CP/ougj/6LoI/+i6
+ CP/ougj/6LoI/+i6CP/ougj/6LoI/+i6CP/ougj/6LoI/+i6CP/ougj/6LkJ/+i5Cf/ougj/6LsI/+m7
+ Cf/puwn/6LoI/+i6CP/ougj/6boJ/+i5Cv/ougj/6LoI/+i6CP/ougj/6bsJ/+i7CP/puwj/6boJ/+i5
+ Cf/pugr/6LkJ/+m6Cv/puQr/6LoJ/+m7Cf/pugn/6LkJ/+i5Cf/qugj/6rkI/+q5Cf/otwf/6boJ/+i6
+ CP/ouQn/6LkI/+i6CP/ouQn/6LkJ/+i5Cf/ouQn/57gI/+e4CP/ouQn/6LkI/+i5CP/ouQn/6LoJ/+i6
+ CP/ouQj/6LkI/+i5Cf/ouQj/6LkI/+i5CP/nuQj/6LkI/+i5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5
+ Cf/ouQj/6LkI/+i6B//ouQj/6LoJ/+i5CP/ouQj/6LoI/+i6CP/ougn/6LoI/+m7Cf/ougn/6LoJ/+m7
+ Cf/pugn/6LoJ/+i7CP/ouwj/6LoI/+i6CP/puwn/6bsJ/+i6CP/ougj/6LoI/+i6CP/ougj/6LoI/+i6
+ CP/puwn/6boJ/+i6Cf/ougj/6bsJ/+m7Cf/ougj/6LoI/+i6CP/puwn/6LoI/+m6Cf/ougj/6LoI/+i6
+ CP/ougj/6LoJ/+i6Cf/pugn/6boJ/+m6Cv/pugn/6LoJ/+i5Cf/pugn/6boJ/+m7Cf/ouQn/6LkK/+i5
+ Cf/pugj/6roI/+q5CP/otwf/6bkI/+i6CP/ouQn/6LkI/+i6CP/ouQn/6LkJ/+i5Cf/nuAn/57gI/+i5
+ CP/nuAj/6LkI/+e5B//ouQn/6LkI/+i6CP/ougj/6LkI/+i4Cf/ouQn/6LkJ/+i5Cf/ouAn/6LgJ/+i5
+ Cf/nuAj/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ougj/6LoI/+i6CP/ougj/6LoI/+i6CP/ougj/6LoI/+i6
+ CP/ouQn/6boJ/+m7Cf/ougj/6bsJ/+m7CP/puwn/6bsJ/+m6Cf/puwn/6bsJ/+m7Cf/pugn/6bsJ/+m7
+ Cf/ougn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6LoI/+m7Cf/puwn/6bsJ/+i6
+ CP/puwn/6bsJ/+i7CP/puwn/6bsJ/+m7Cf/puwn/6LoJ/+i5Cf/pugr/6boK/+m6Cv/puwn/6LoJ/+i5
+ Cf/puwn/6bsJ/+i6CP/ouQn/6LkJ/+i5Cf/nugj/6boI/+m5B//otwf/6LkI/+e5B//nuAj/6LkJ/+e4
+ CP/nuAj/57gJ/+e4Cf/nuAn/57gI/+e4CP/nuQf/6LkI/+i5Cf/ouQn/6LkJ/+i5CP/puAj/6bkJ/+i5
+ Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5CP/ougj/57kH/+e5B//ouQn/6LkJ/+i5
+ Cf/ouQn/6LkJ/+i5Cf/ougj/6LkI/+i5CP/ougj/6boJ/+i5Cf/pugr/6LoJ/+m7Cf/puwn/6bsK/+m6
+ Cv/pugv/6boL/+m6C//pugv/6rsK/+m7Cv/puwn/6bsJ/+m7Cf/puwn/6rwK/+m8Cf/pvAf/6bwJ/+m7
+ Cv/puwn/6bsJ/+m7Cf/puwn/6LoJ/+i5Cf/ougn/6LoI/+m7Cf/pugr/6LoJ/+m7Cf/puwn/6LoI/+i6
+ Cf/puQr/6LoJ/+m7Cf/puwn/6LoI/+m7Cf/pugr/6LkJ/+m6Cv/pugr/6boK/+i5Cf/nuQj/6boI/+m5
+ B//otwf/6bkI/+i4B//mtwf/57gI/+e4CP/mtwn/57gJ/+e4Cf/ntwn/57gI/+e4CP/nuAf/6LkI/+i4
+ Cf/nuAj/57gI/+i4CP/puQn/6LkJ/+i5Cf/nuAn/6LkI/+e5CP/ouQn/6LkJ/+e4CP/ouQn/6LkJ/+i5
+ Cf/ouQj/57kI/+e5CP/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+e5CP/ouQj/57kI/+i5Cf/puwn/6boK/+m6
+ Cv/ouQn/6LkI/+m5B//puQf/6bkI/+q6CP/qugj/6roI/+q6CP/qugn/6bwJ/+q8Cv/puwr/6bsL/+q8
+ Cv/qvAr/6rwL/+q8Cf/qvAn/6rwJ/+m8CP/qvAn/6bwI/+m8CP/puwn/6LoJ/+i6Cf/puwn/6bsJ/+m6
+ Cf/pugr/6boJ/+m7Cf/puwn/6LoI/+i6CP/ougn/6LoJ/+i6Cf/ougn/6boJ/+i6CP/pugn/6boJ/+m6
+ Cv/pugr/6LoJ/+i5CP/pugj/6bkI/+m4B//otwf/6bgI/+i3B//mtwj/5rcI/+a3CP/mtgr/5rcJ/+a3
+ CP/mtwj/5rcI/+e4CP/nuAj/57gI/+e4CP/nuAj/57gI/+i4CP/nuAj/6LkJ/+e4CP/nuAj/57kI/+e5
+ B//nuAj/6LgI/+i5Cf/ouQn/6LkJ/+i5Cf/nuAj/6LkJ/+i5Cf/ouQn/6boK/+i5Cf/ouQn/6LkJ/+i6
+ Cf/pugr/6bkK/+i5CP/puQj/6bgG/+i5CP/ouwz/6bsQ/+m9Ff/pvRj/6r0a/+m9F//pvhf/6r8X/+q+
+ FP/qvhD/6r0N/+q8Cv/quwn/67sH/+u7CP/quwn/6rwL/+q8C//qvAv/6r0J/+q9CP/qvQj/6r0I/+q9
+ CP/puwn/6bsJ/+i7CP/puwn/6bsK/+m6Cv/puwn/6bsJ/+i6CP/puwn/6boJ/+i6CP/ougj/6boJ/+m5
+ Cv/ouQn/6LoJ/+i6CP/puwn/6boJ/+i5Cv/ouQr/6LoI/+i6B//qugj/6rkJ/+m4CP/ntQn/57YJ/+a2
+ Cf/ltQn/5bUJ/+W0Cv/ltQn/5LUI/+W1Cf/ltgj/5rcI/+a3CP/mtwj/5rcI/+e4Cf/nuAn/57gI/+e4
+ CP/nuAj/6LkJ/+i5Cf/nuAj/57gI/+i5Cf/nuAj/6LkI/+i6CP/ouQn/6LkJ/+i5Cf/ouQn/6LkJ/+i5
+ Cf/ouQn/6boK/+i5Cv/ouQr/6boJ/+m5B//pugf/6boK/+m8EP/pvRr/6MIl/+nAKf/muyT/5roe/+e8
+ Hv/nwCX/58Y0/+jJP//nyT//5sk9/+fJP//pyjz/6Mk7/+nINv/pxi//6sQm/+rCGP/rvg//670I/+y7
+ B//rvAn/6r0K/+q+Cf/qvQj/6r0I/+q9CP/qvAj/6bwH/+m8B//puwn/6bsK/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6LsI/+m7Cf/puwj/6boJ/+m6Cv/ouQn/6LkJ/+m6Cv/pugn/6LkJ/+m6Cv/pugr/6LoJ/+i6
+ CP/quQn/6rgJ/+m4CP/mtAj/5rUJ/+OzCP/lswn/5LMI/+SyCP/ksgj/5LMI/+WzCf/ksgr/5bMJ/+W1
+ B//ltQj/5rYJ/+a3Cf/mtwj/5rcI/+e4CP/nuAj/57gI/+e4CP/ouQn/57gI/+i4CP/nuAj/57gI/+i5
+ CP/ouQn/57kJ/+i5Cf/ouQj/6LkI/+i5Cf/ougr/6LoK/+i5Cf/nuQb/6boL/+m9Fv/owCP/58Ux/+fI
+ Pf/nxTr/5bop/+SwFv/jrg7/4KkN/92hC//ZoAv/26MQ/96wIP/iwzj/5spG/+jKRv/oy0X/6MtH/+fK
+ Sf/mzEf/58xJ/+jMRv/oyz//6Mgy/+nDIf/qvxL/67wH/+q9CP/rvQr/6r0K/+u+Cf/rvgn/6bwH/+m8
+ B//qvAn/6bwI/+m7CP/puwn/6bsJ/+i6CP/puwn/6boJ/+i6Cf/ougn/6boJ/+m6Cv/pugn/6boJ/+m6
+ Cv/pugr/6boK/+i5Cf/ouQn/6boJ/+i6Cf/puQj/6rkI/+m4B//lsQr/5LIK/+OxCf/ksAr/5LAK/+Sw
+ Cf/ksAr/5LAK/+SvCv/krwr/5bAK/+WyCv/ksgn/5LMI/+W1Cf/ltgf/5bYH/+a3Cf/mtwj/57gI/+e4
+ CP/nuAn/57gI/+e4CP/nuAj/57gI/+i4Cf/ouQn/6LkJ/+e4CP/ougj/6LkJ/+i5Cv/puQj/6bkI/+i9
+ E//nwSP/5sQy/+bGPf/myEL/5sZA/+O6K//irhL/4qsI/92jC//Wlw7/1ZIM/9aWCv/Zmwr/3qIK/+Kp
+ CP/krxD/4bco/+XHQ//nzU7/6MtL/+jLSf/oy0r/6cxK/+nMS//nzUn/581L/+bNS//nzET/6Mcx/+rB
+ Gf/svgr/7b4I/+u/Cv/rvwr/674J/+q9CP/qvQj/6r0I/+m8B//puwr/6bsJ/+m7Cf/puwn/6bsK/+m5
+ Cv/pugr/6LkK/+i5Cf/puwn/6LoJ/+i5Cf/pugr/6boK/+m6Cv/pugr/6boK/+m6Cf/qugj/6roI/+q6
+ CP/irQr/4q0K/+GsCP/gqwn/4KoJ/+CqCv/gqwn/4asJ/+KtCf/jrgn/4q0K/+KuCv/jrwn/47AI/+Oz
+ CP/ktAj/5LUI/+W2Cf/mtgr/5rcJ/+e4CP/nuAn/57gI/+e4B//nuAj/57gI/+i5Cf/ouQn/6LkJ/+i5
+ Cf/pugr/6LkJ/+i5CP/pvRP/6MIn/+fFOP/lxj//5shD/+fHP//luy7/4q4V/+KrB//dowr/zowQ/8h/
+ Dv++cw//pVcW/44/HP+ANR//hDkg/5hTGf/Dhg//5rIH/+i3E//hvjH/5ctM/+nOU//pzU//6s1O/+rO
+ T//pzU3/6M1M/+jNTP/ozkz/6c5O/+rNSf/pyjj/6cMd/+y/Cv/tvwj/7L8L/+u/Cv/rvgn/674J/+q9
+ Cf/qvAr/6rwK/+m7Cf/puwn/6boJ/+m5Cv/pugr/6LkK/+m6Cv/pugr/6LoJ/+i6CP/ougn/6boJ/+m6
+ Cf/ouQn/6LoJ/+i6CP/ouQn/6bkJ/+m4CP/Ymgv/1pkM/9SXDP/SlQz/0JIK/8+QC//Ojgz/z48M/9OU
+ DP/ZoAv/36kK/+KsC//gqwr/4KwK/+GvCf/isQn/47II/+WzCP/ltQn/5rYI/+W3B//mtwj/5rcI/+e4
+ CP/nuAj/57gI/+i5Cf/ouQn/6LkK/+i6Cv/ouQj/6bwS/+fCKf/mxTn/5cU+/+fIQP/nx0H/5r4x/+Ku
+ GP/iqwr/4KYL/9CODv+8cRP/vG4U/6BPGf9qFib/VgQt/1UBL/9VAi//VAEv/1MAL/9XCCz/hT8e/8aO
+ Dv/stwf/6bsZ/+PCOv/nzVP/6dBY/+nPVP/pz1P/6c9S/+jPUP/oz1H/6M9P/+fOT//ozlH/581O/+jJ
+ Of/rwxf/7r8I/+2/C//svwv/7L4J/+u+Cf/rvwn/674J/+q9Cf/pvAj/6bsJ/+m6Cf/pugn/6boJ/+m6
+ Cv/pugn/6boJ/+m7Cf/puwj/6bsJ/+i6CP/ougn/6LoJ/+i6CP/ougn/6bkI/+m4CP/Wlgv/05UM/9GT
+ DP/QkQv/zo0L/8yLDP/LiQv/yYUM/8aDDP/Egg3/y4oN/9aZC//dpwv/3qoL/96qCf/frAn/4q4J/+Sv
+ Cv/jsgn/5LQJ/+S0CP/mtgj/5rcI/+a3CP/muAj/57gI/+i5Cf/ouQv/6LoJ/+q7C//owCD/5sU2/+bF
+ Pf/mxkD/58hA/+W/M//irxv/4qkL/+CnCv/SkhD/vnQS/7JkFf+zaBX/l0Qe/10HK/9ZCC3/Xg0s/14M
+ Lf9hDCz/Xwws/18NLf9eCy7/VAEv/1gKK/+ISB3/zpgN/+26C//nvR//48VC/+jRWv/p0Vz/6dBY/+nQ
+ V//p0Vb/6NBV/+fPVP/nzlP/6c5S/+jOUv/nzk3/6ckt/+3CDf/twAr/7cEN/+y/Cv/rvgn/6r4J/+q9
+ CP/qvQj/6rwJ/+m7Cf/puwn/6boJ/+m6Cv/puwj/6LsI/+i6CP/puwn/6LoJ/+m7Cf/pugj/6LoI/+m6
+ Cf/ougj/6boI/+q6B//UlQv/05QK/9GRC//Ojwn/zYsL/8uIC//Jhgv/x4QK/8aCC//Ffw3/w3wM/8N8
+ Df/JhQz/1ZgL/96mCv/gqgr/4KoJ/+GsCf/jrgr/47AK/+OyCf/ktAj/5bUJ/+a2Cf/mtwf/57gI/+i5
+ Cf/ouQn/6L0U/+jBL//kxj3/5cc+/+bHPv/kwDb/4bEc/9+pDP/hqAv/1ZcO/8B3Ef+xZRP/rWEV/65h
+ F/+VQB//Xwks/1wJLP9hCi3/YAkt/2EKLf9gCS3/XgYs/1wFK/9bBiv/Xwwt/18LL/9TAC//Ww0r/5BR
+ G//Unwz/7r0M/+jAJf/lyk3/6dFh/+rRXf/p0Vn/6dBZ/+nRWv/o0Fn/6M9W/+fPU//mz1L/5tBV/+jN
+ Qf/rxRn/7cEJ/+zBDf/qwAv/678K/+y+Cf/rvgj/6r0J/+q7Cv/puwn/6bsJ/+i6Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+i6Cf/ouQn/6LkJ/+i5Cf/quQn/6rkI/+m6B//UlAv/05ML/9CPC//OjAv/zIoK/8mH
+ C//IhAv/xoEM/8R+DP/DfQz/w30M/8J7Df/AeQ3/wHgO/8aCDv/Ulwv/3qYJ/+CqCv/gqQr/4KsK/+Ct
+ Cf/isAn/47IJ/+S1CP/mtwf/6LgI/+i5Cf/lvRj/5MIz/+XFPP/kwjX/47ko/9+wG//fqA3/36cJ/9ea
+ Df/DfRH/s2cR/65iE/+pXRb/pVYb/5RAIv9lDCz/Xggu/2AKLf9gCS3/YAou/1wHLP9hCiv/bBov/3Mi
+ Nf9tHDP/YAos/1wEKv9gDC7/XQkv/1IALv9eEyn/nF0a/92pCv/xwQ7/6cIv/+XNVf/m02T/6dJe/+rS
+ Wv/p01r/6dJa/+nRWP/p0Ff/6M9U/+fPVv/nz1H/68ko/+3CCf/twQz/7MAM/+y/Cv/rvgn/674J/+q9
+ Cf/qvAr/6bsJ/+m7Cf/puwn/6LoI/+m7Cf/puwn/6LoI/+i6CP/ougn/6boJ/+i5Cf/puQn/6bkI/+m5
+ CP/Ukgv/05IL/9CPC//Nigz/y4kK/8iFCv/Hgwv/xX8N/8R9DP/Cewz/wXkM/8B4Df++eA3/vncO/7tz
+ Dv+9cw3/xX4N/9OVDP/dpgn/3qgL/92oCv/fqwr/4K0K/+KxCP/jswj/5rUK/+W7Gf/ivy//48At/+O0
+ H//eqBH/3aQK/96oCP/ZnQ3/xoAQ/7RoEv+uYxP/rGAU/6ZVGf+hTh7/kj4j/2gRLP9eBy7/YQks/18I
+ LP9hCi3/XAYs/28dL/+RSTv/nFdE/6BdSP+hXUz/lU1G/3YnN/9dCSv/WgYq/2ENLf9dCS//UgAw/2Ma
+ Kv+nbBf/5bII//DFEf/oxDj/589b/+rUZf/q01//69Nd/+vSXP/q0V3/6dFa/+jRVv/nz1f/5tBX/+nL
+ Nf/swwz/68IM/+zBDf/swAv/678J/+u+Cf/qvAr/6bsJ/+m7Cf/puwn/6LoI/+m7Cf/ougj/6bsJ/+i6
+ CP/ouwj/6LoJ/+i5Cf/ouQn/6bkJ/+q5Cf/Skgv/0pIL/8+OCv/Oiwv/y4gL/8iEC//GgQv/xH4M/8J8
+ DP/BeQ3/wHgL/793DP++dQz/vHQM/7tzDf+6cQ7/t20P/7huEP/BfA//0ZUM/9ymC//dqQr/3akJ/96q
+ Cv/grQv/4rIO/+C0Fv/gsBf/3agO/9qjCP/epgv/2qAN/8uEEP+4ahH/r2IS/6xgFP+mWBj/oE8Z/5pI
+ H/+POiT/ahMr/14ILP9hCi3/Xwgs/2AJLf9bBiv/bx4u/5dSPv+bWUX/nFhG/5xVRP+dVUT/o11J/6dk
+ UP+VTkf/cyQ0/1sHK/9bBS3/Yg0u/1wHL/9PADD/bCUl/7R6E//quAj/8cUZ/+bIQf/m0WD/6NVm/+nT
+ Yf/q0mD/6tJd/+vSW//p0Vn/6NBY/+bQWv/pzT3/7cMO/+zCDP/swQz/6r8K/+q/Cv/rvgn/674J/+q9
+ CP/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6LoI/+i6CP/quQn/6rkJ/+m4CP/Tkwv/0pIK/9CP
+ Cv/NjAr/yogK/8iFCv/GgQr/w30K/8F7C//AeA3/v3cM/751DP+8cg3/unEN/7hvDv+4bg//t2wP/7Zr
+ D/+zZhH/tGcR/795D//Slgv/3KYJ/96qCv/eqgv/2qQN/9abCv/WmQf/2Z8K/9ifDP/NiA//uW0R/69i
+ Ev+sYBP/p1sV/59RGf+ZSBz/kj4j/4UxKf9pFCv/Xgct/2EKLP9fCCz/YAot/1wGLP9rGC3/lE8+/5lV
+ Qv+QRDX/iz45/5JPT/+dX17/nVxV/5pPQf+iW0f/p2VS/5JLR/9vIDP/WgYr/1wIK/9hDi7/WQUv/1IA
+ L/90LyT/vIUU/+u+C//wyCD/6MtK/+fSZ//q1Wb/6tNg/+rSX//q0l3/6tFd/+nQWP/o0lr/6c0//+zE
+ D//sxAz/7MEM/+u/Cv/svwr/674J/+u9CP/qvAr/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/pugn/6LoJ/+i6
+ CP/puQn/6bkI/+m4B//TlQr/05QK/9CQCv/NjQn/yokK/8iGCv/Hggr/xH4L/8J7DP/AeA3/vnUM/7xz
+ DP+5cQz/uXAN/7dtDv+1ag7/s2YQ/7JlEP+zZBH/r2IS/6teFP+tXxT/uHAQ/8OADf/JhQv/y4YK/8yI
+ Cf/Jhw3/wnsQ/7ZpEv+tXhT/qV0U/6ZZFv+fUBr/mkgd/5I+I/+IMyj/fCoq/2kULf9eCSz/YQot/2AJ
+ Lf9hCiz/XQYs/2cSLf+RSzz/mFVC/4k+Mv+fbnT/y8DK/9vh6v/e6PP/3OLt/8m0uf+mbmb/nFFC/6Zg
+ Tv+nZlX/jkhG/2sbMv9ZBCn/XAkr/2AOLv9WAzL/UgMv/3s5Iv/EkRL/8sQN/+/KKf/lzlT/59Vq/+rT
+ Zv/r02H/69Jg/+rSXP/p0Vn/59Jb/+nOQP/sxA//7cIO/+zBDP/svwr/7L8K/+u+Cf/qvAr/6rwK/+m7
+ Cf/puwn/6bsJ/+m6Cf/pugr/6LoJ/+m7Cf/ouQn/6bkI/+m5B//Vlwr/1JYK/9KSC//Pjwr/zIoL/8mH
+ C//IhAv/xYEK/8N8DP/AeQv/vXYL/7pzDP+5cA7/t24N/7ZrD/+1aA//smUR/65hEv+rXhP/qVwU/6lc
+ Fv+nWRf/pVQZ/6RSGP+lVRj/qFgX/6pYFv+nVhf/pFUY/6NWF/+iUxf/nk0b/5lIHv+SPyL/hzQl/30p
+ Kv90IC7/ZxQt/18KLP9gCS3/YAkt/2EKLf9eBy3/Yw4s/5BGO/+bWEP/hjsy/7GRmf/r+P//6PX7/+Lu
+ 9v/j8fn/4u/3/+b3///h7vj/xLC0/6VqYv+dU0X/p2NS/6hlWP+MQ0b/aRcy/1kEKv9eCS7/YAww/1UB
+ Mf9WBi//hEQi/82cD//zyA//7s01/+bRYv/n02v/6tNk/+rTYv/q01//6dFa/+jSXv/pzDn/7cQL/+zE
+ D//swgv/68AK/+q+Cv/rvgn/6r0J/+m7Cv/puwn/6bsJ/+i6CP/puwn/6boJ/+m6Cv/ouQn/6boI/+m5
+ B//Xmgn/1pgJ/9SUDP/QkQv/zY4K/8uKC//Jhwv/xoMK/8R/C//Cegv/vncM/7t0Df+5cA7/t20O/7Vp
+ D/+yZg//sGMR/61gE/+pXBT/pVkV/6JVGP+iUhr/oVEb/6BQG/+fUBr/n08b/59PHP+eTRz/m0sc/5dH
+ Hv+UQiD/kDwi/4kzJv9+Kir/dSEu/2wYMf9kDzD/Xwot/2AKLf9gCS3/YAkt/14ILv9gCyz/i0E4/5xa
+ Q/+HOzD/rIeM/+36///h6e//4ev0/+Xy+P/c4+n/5fb8/+Pz+//h7PX/5fr//9/r8//Cqq7/pGZf/59W
+ SP+raFf/pWVY/4Y+RP9mEzD/WQUq/14LLf9hDC//UwAx/1cLLf+PUR//2KcL//XPF//s1Ez/5tJs/+nT
+ ZP/p1GH/6tJg/+rQXP/p0l3/68ws/+7ECf/sxA3/7MIL/+vACv/rvgn/670J/+q8Cf/pvAj/6bwI/+i6
+ CP/puwn/6bsJ/+i6Cf/ouQn/6bkJ/+q5B//ZnQv/2JwK/9WYCv/SlQr/0JIK/86OC//Ligr/yYYL/8eC
+ DP/Dfgv/wXsL/714DP+7cwz/uG8O/7VpEP+xZhH/rmES/6xeE/+oWxX/pFcW/6FSGv+cThv/mUsc/5dH
+ H/+WRSD/lkMi/5NBIf+RPyD/jTsh/4g2Jv+EMCn/fikq/3YiLf9vGTD/aBMw/2EOL/9eCi3/Xwot/2AJ
+ Lf9gCS3/Xgku/1wJKv+FPTT/m1pE/4k+MP+keX//6vb7/+Hq8P/g7fb/6Pb1/6d9eP+UWVj/s4yK/9fT
+ 1v/o+///4vL6/9/x9//m/P//3efw/76ipv+iZFz/oFpM/6lrW/+iYlr/gThD/2IQLv9aBSr/YA0t/14L
+ L/9SADD/WxEs/5dfGf/ftAr/9do+/+rXav/o0WT/6tRj/+rSYP/q0l3/6dJa/+7KHv/sxAr/7MQN/+zC
+ Cv/swAr/674J/+q9CP/pvAf/6bwI/+m7Cf/puwn/6bsJ/+i6CP/ouQn/6bkJ/+q4CP/boAr/2qAK/9ic
+ C//VmQr/0pUK/9CRCv/Ojgv/zIoL/8mHC//Hgwr/w38M/797DP+9dwv/unMN/7duDv+zaQ//rmQQ/6pe
+ E/+nWRb/olUY/55RG/+bTR3/lkcf/5JBIv+PPSX/jDol/4g0Jv+DMSf/gCso/3slK/91IC3/bhow/2cU
+ Mv9iEDL/YA0v/14KLf9gCS3/YAkt/2AJLf9fCi3/XAYt/4A2M/+bWUT/jEEy/51rcf/m8ff/4u3x/+Dr
+ 9f/p+Pr/qn91/4E+N/+WVlP/mlpY/5hbVv+3j47/3Nzf/+f9///f8ff/4vX7/+f+///b4ur/vZue/6Jg
+ W/+iXVD/q2xf/6BfWP9+M0D/YA0t/1kEKv9iDS7/Xgov/1EAMf9hGSr/pGwX/+PNUP/x4Gv/59Fk/+jU
+ Zf/p02H/59Jg/+rRT//uxxD/7sUN/+vDDP/swQz/68AL/+q+Cv/rvgn/6bwJ/+m7Cf/puwn/6bsJ/+i6
+ CP/ougj/6rsJ/+q5B//dowj/3aQK/9qhCv/YnQv/1poK/9OWCv/Rkwz/zo8L/82MC//LiAv/x4QL/8N/
+ C//BfAv/vncM/7pyDf+3bQ//smgP/61hEv+pXBX/o1YY/59QG/+ZSh7/mEgg/5ZDIf+MOCb/gy8n/4Ev
+ Lf95JS7/cxwv/2sZL/9qFTL/ZREz/2APMf9fDi//Xgot/18JLf9gCS3/YAkt/18KLv9bBiz/fy8z/5xZ
+ RP+PRDP/mGFj/+Po8f/j7vT/3+n0/+v7/P+thXv/hEA4/5JVVf+tkKj/q4ub/6Fnbf+aWln/nmFf/7ue
+ mv/g7fL/4fP6/97v9f/i9f3/5vz//9nd5P+2kpX/oV1Y/6RfU/+rbmD/nVxW/3kvPv9eCy3/WgYq/2EO
+ L/9cCTL/TwAu/2QiLP+5mUz/8N9o/+fSZv/o02X/6NJf/+jSYv/rzjn/7sUK/+zED//swgz/68EL/+u/
+ Cv/rvgn/6r0J/+m7Cf/qvAr/6bsK/+i6Cf/ougj/6LoI/+m5CP/epgj/3qYJ/92lCf/bogv/2aAJ/9ec
+ Cv/VmAr/0pQK/8+QC//OjQv/y4kL/8iGC//GgQv/wXwL/714DP+7cw3/t24N/7JoEP+sYRT/p1sW/6JU
+ Gf+eUB3/izoi/3AdJ/9eCCX/YA0s/2AJIP9wGCj/cx40/2gVMv9lETH/Yg8w/18NMP9fCy7/Xwkt/2AJ
+ Lf9gCS3/YAsu/1kFK/94KTL/mlhD/5JIN/+RV1j/3uHq/+Tw9v/g6vT/6fv9/6+Mf/+FQDn/k1NP/7KU
+ r//EvNf/w7jU/72pxv+uiZz/oWlz/6Nkb//c3eT/5Pv//+f7/f/l+v7/5Pn+/+P4/f/m/f//1dje/7KM
+ jP+gXlf/pGJY/6tvY/+bWVf/dio7/10IK/9aByv/YQ8v/1oHMP9QACn/soxM//Hfav/m0mX/6tRk/+rR
+ X//o013/7soi/+7FDP/rxA7/7MIM/+vAC//rvwn/670K/+m7Cv/puwn/6bsK/+i5Cf/ougj/6LoJ/+e4
+ CP/ksgb/4awH/96nCf/dpgr/3KQK/9uhC//Yngv/1psK/9WYC//Skwv/z48L/8yLC//KiAr/x4ML/8N9
+ DP++eQz/u3QN/7hvD/+zaQ//rGIT/6tgF/+LNiL/XQku/1EAJP+DU2T/08nD/554hv9mFzH/XwMc/20W
+ Lv9sGTT/ZREw/18LLf9gCS3/YQkt/2AJLf9eCy3/WgUs/3MjMP+ZVUH/k0o5/45QT//Y2OL/5fL5/97o
+ 8f/k8fj/t5KI/4c8N/+STU3/sI+n/8O41P/Uzt3/19Xh/8W/2f/FwOD/uaTB/8u7y//m/v//4e3s/6l4
+ ff+whov/09HT/+D1///T5vr/ydv6/8nc/v/FwtT/sYSG/6JeVf+mZVr/rW9l/5hUVv9wJTn/WQgr/14L
+ Lv9cCDP/ZB8u/+TOYP/s12r/6dRm/+jTY//q0mT/6tFK/+7HDv/txQ7/68MN/+rCDP/qwAr/6r0K/+q8
+ Cv/qvAr/6bsJ/+m7Cf/puwn/6boI/+m5B//puAf/6LgH/+SyCP/hqwn/3qYK/92lCv/cowr/2aEI/9ie
+ Cv/Xmwv/1JcL/9GSC//Ojgr/zIoM/8mGDP/FgQz/wXwM/713DP+5cQ7/tWwR/61gF/9kEin/XQsw/1MC
+ If+1n53/6+nd/+jn3f/UzMr/lWp3/2ESKf9gByD/aBIw/2AJK/9gCCz/YQkt/2AKLP9bBSv/bx0u/5hT
+ QP+TTDr/jEhG/9PO2f/m9Pn/3ujv/+Py+v/S1d//fTA3/49DQf+ujKL/w7rT/9XR3P/p8fL/1dDf/83G
+ 2//Bs9L/0s7m/+X9///n9fL/qGlq/41HUP+hZ23/l11n/515jf+vq9H/vs78/73N/v+/0/7/yd39/8S9
+ zP+tf3//oV5W/6hoX/+rb2b/kk9U/2wgOf9cCi3/UgIn/8CcU//z4m7/6NNn/+nWZv/q02D/6dRi/+3N
+ Kv/vxgv/7MUP/+vDDP/qwQv/6r8K/+q9Cv/qvQn/6bwI/+m7Cf/ougj/6rsJ/+q5CP/otwf/6LkI/+e5
+ CP/mtwn/5LIJ/+CrCf/dpgr/3KQJ/9uiCv/aoAr/2Z4K/9ecCf/Ulwr/0JIK/82NC//LiAv/x4UM/8OA
+ DP++eA7/v3YQ/6peF/9cDCr/YA0w/1QEI/+3oJ7/5eHY/9rRzf/i3dj/6/Hs/87JzP+IV2T/XAYg/18I
+ K/9gCCz/YAks/1wFLP9rGS7/lFA9/5ZOPP+JQT3/zcPN/+n1+//g6O//4ez0/9/s8//i8/7/vK6+/6R/
+ lP/EutH/wbPK/8rF2P/Sz97/xrrS/8Sz0P/Qyd//4vX7/+j69v+qcnP/kEtR/5xgbv+jf5r/n3OI/5Ra
+ bP+KTmL/mXOR/7a22v/B2P//v9T9/8Ha///K4Pv/w7nH/616e/+iYFf/rG1j/61wav+TWWb/Vwo2/5Fb
+ Pf/25m//6dVr/+rWav/r1GX/6NRm/+rST//vyBD/7sYQ/+zEDf/qwgz/6sAL/+q+Cf/qvQj/6b0H/+m7
+ CP/ougn/6boJ/+q5Cf/ptwf/6bkJ/+e4CP/nuAj/57kI/+e4CP/jsgn/4KoK/96lCf/cpAr/3KMJ/9qh
+ CP/ZoAn/150I/9SYCf/QkQv/zY0L/8qLC//GhA3/x4IQ/65lF/9bCyr/YQ4x/1MEJP+2oaH/6Off/9XK
+ xv/Eo6L/08O+/+bj4//k6Oj/n2l3/2IKKf9fCSz/XQYt/2cTLf+QSzv/llE//4U9N//Htr//6vb7/9/m
+ 7v/g6/P/3+v0/9/r9v/e7fj/4/j//9rq9P/Mytz/xrzU/8K21P/Bu9f/w7rY/83H4P/h9vz/6P38/6x4
+ ev+OTEv/l09a/5dcb/+ec4f/m2d9/5Vedf+TXHb/j05m/4tNZP+cepL/ur3f/8Td///A2f7/xuH//83h
+ +v/AtcD/rXh4/6ZhXP+5iYn/iFl//2YeJ//r12z/7dlv/+nWbf/q1Wn/6dNl/+nVY//tzCj/7scM/+zG
+ D//rww3/6sEL/+q/Cv/qvQn/6r0H/+m8B//puwj/6bsJ/+i6CP/puAj/6bkI/+i6Cf/ouQj/6LkI/+e4
+ CP/ouQn/57gJ/+OyCf/fqwn/3acK/92mCf/dpQj/26MJ/9mhCv/Ynwr/1p0J/9GWCv/Mjg3/0JAN/7Zw
+ Ff9aCin/YQ4x/1IEJP+3oqL/6uji/9XLyP++mpr/wJuY/9XIxv/f4OL/2tTY/349Uv9XACP/ZhAt/4tB
+ N/+YUT//hDkz/76or//s9vz/4Obs/+Lq8f/g6vL/3uny/9zn8f/Y3er/z83f/8fE2//IxN7/wrXP/7eh
+ uP+7p7v/tZ+z/7Ocr/+8sL7/n3F//4hHSf+WUVn/l1Vo/5pkdf+aYXP/nmh7/59uhf+faoX/p4Ce/6OB
+ nv+WYXn/k151/6GFoP+8xef/w9r8/8HZ+v/H4///zeD2/8Gxuv+ygIP/pHWV/1cOL//IqVj/8+R1/+jW
+ b//p12v/6tVn/+jVZ//r0Ef/78gN/+3HEf/sxA7/68EL/+u/Cv/qvgn/6bwI/+m7CP/ouwj/6bsJ/+m6
+ CP/puAj/6bkI/+i6CP/ougf/6LkI/+i5Cf/ouQn/6LkK/+i6Cv/ouQn/5bUJ/+CtCf/fqQf/3agI/9yp
+ Cf/dqQr/3qoI/92oCP/Xnwv/2J0N/756Fv9bCyr/YQ4w/1EEI/+4oqL/6+nk/9XKyf/AnZ7/xaGf/9jN
+ y//Z2d3/4+fo/72uu/9TAyX/cB8u/5NKPP+HOjL/t5mh/+v1+//g5ez/4unw/+Ho8P/f5/D/2+Hr/9jY
+ 4//S0uL/y8rh/8a40f+phaH/czRW/2UXQP9wKUr/i1Jo/5xref+JSmD/byA5/4hAUP+UV2b/m2R1/5xl
+ eP+bYXb/j09n/4E/Wv94MFH/eDxg/39Sd/+CSm3/j1x4/5hpf/+fb4P/ur/e/8Xf///K5P7/xd///9jw
+ ///l8fP/y77Q/2AbR/+dbD//9+p4/+fVcf/p2G3/6dZq/+jUZP/p013/7ssc/+/HD//sxQ7/68IM/+u/
+ C//pvgn/6bwJ/+q8Cv/puwn/6rsJ/+q6CP/puQf/6roJ/+i5Cf/ougj/6boJ/+i5Cf/ouQn/6boK/+i5
+ Cv/puwn/670K/+u9C//mtwj/4q8J/+GrB//jsAf/47IL/+KxCf/frQj/5LAK/8aIFv9aCir/YQ4w/1EE
+ I/+4o6P/6Orm/9bPz//Bnp7/wpua/9rPzv/f4eb/29zf/97i6P9wNE7/Xwwn/3wrLP+meH//6vP7/97k
+ 6v/h5+7/4efu/+Dl7v/b3uj/2Nzo/9bb6f/JwNH/s5Gk/5xgd/9dDTD/Ywsb/4QrF/92IBn/Xwgo/3Aj
+ Rv+ERF7/eTlO/3AoQP90LUb/mF1t/4dEXP9oGjn/VAIk/0wAGf9PABr/TgAb/04AGP9MABj/TQAa/2Ia
+ Ov+GVGv/u8He/8LZ+f+kpMf/w9Pn/+v7/v/m9fj/8f///45si/93Myn/8uR5/+jZdP/p2W//6dZq/+nV
+ Zf/p1Wj/7c8z/+/HDv/rxg//7MMM/+zAC//qvwr/6r4J/+q8CP/puwn/6bsK/+i6CP/ouAf/6LoJ/+i6
+ Cf/ougj/6LoJ/+i6Cf/ougn/6LoJ/+m6Cf/puwn/6rwK/+u+Cv/qwQr/68EL/+e/FP/mvBj/5rUG/+a2
+ CP/lswz/6rsL/82UFP9bCir/YQ4v/1EEI/+4pKX/5ujl/9zZ2f/Xzs7/zri4/9vQ0f/f4+j/3N3h/+jy
+ 9P+bfY3/VAAb/20gMP/Owsn/4unw/97i6v/f5ez/3+Ps/9re6P/Y4Ov/0M7c/7iap/+fbHr/mV1v/2sd
+ Pv9lECL/mkUU/7FbDf+8aAv/o0wP/3gcGP9iCCv/biRH/2ISMP9XACL/Zhg0/1cGJ/9QAB7/dyxD/6Bx
+ fP+4nKf/wbS9/8Gzwf+zmq3/j2aA/2MfPf9IABD/ZidE/2wxUf9IABX/Yyg9/93a2//u+fz/7////52N
+ pv9pKCf/8ON6/+rbdv/p2XL/6dhu/+rVaf/o1Wj/6tJL//DJEf/tyBH/7cQO/+zCDP/qwAv/6r4J/+q9
+ CP/qvAj/6rwJ/+i6CP/nuAj/6LoJ/+i7CP/puwn/6LsI/+i6CP/puwj/6LoI/+m7Cf/qvAr/6r0J/+y/
+ Cv/swAz/7MIJ/+rLM//m0VT/58s6/+i/HP/otgr/7r8L/9GZGP9bCiv/YQ4w/1IEJP+4pKb/6Ovp/9TN
+ z//Fqaz/1MnJ/97g4f/h5uv/3uTq/+Lp7//S0t3/XxYw/1sIH//Qw8v/4ujx/9vc5P/d4ur/29/p/9fc
+ 6P/CtcD/qHiD/51jcf+eZ3f/gT1W/10GJv+POBf/rFUO/7hjDf+4ZQv/vWoK/7xlDf+fRhD/dBca/1sE
+ KP9qJ0T/Uw4s/2UYL/+8lZj/7/Lw//v////5/v7/+P////n////9/v7/+P///+Pt9P+qlKn/Xxw6/1QC
+ IP+JTmj/WBQv/2YrO//r7uz/6f7//2UtWP+MWD7/9uyC/+nZd//p2nX/6dhx/+nWbf/o1Gr/6dRd//DM
+ Gv/vyBH/7MUP/+vDDP/rwAv/678K/+q9CP/pvQf/6bwI/+i6CP/puQf/6rsJ/+i6CP/puwn/6bsJ/+m7
+ Cf/puwn/6bsI/+m8B//rvQj/6r4J/+vAC//qwQv/68QO/+nOR//n0Vj/6NRc/+nUXP/nzEb/8Moh/9Sb
+ Fv9bCyr/YQ4x/1MFI/+4pab/6evq/9TP0v+/n6X/waKl/9HK0P/RxtH/3OHn/9/m8P/k8/7/tau8/1QB
+ Hf+DSVT/5Obq/+Dp8f/Y2eX/19vn/8/L2f+lcXz/oWp0/51peP+VWm3/YRAz/3ohHf+lThD/t2AN/7tl
+ DP+7ZQv/umUL/7pjDf/CbQ7/pE4Z/1AAIP+heoP/u622/9bHyf//////4u/y/62isP+KX3L/fUhd/4JQ
+ Zf+dfYz/zcvN//f+/f//////4eXs/9HH0//7////xLvK/04BI/9+WGj/elFz/1YNIv/XxXP/8OWA/+nc
+ ef/r3Hb/6tlz/+jXbv/q1Wv/6dVo/+7PK//uyA//7McP/+vDDv/swQz/678K/+q9CP/pvAf/6bwI/+i6
+ CP/puQf/6rsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bwJ/+q8Cf/qvQn/6r8K/+vADP/rwwv/7Mca/+fS
+ VP/o0Vr/6dFc/+rTX//n1GT/7uBj/9KtRv9aCSj/Yg8x/1MFI/+4pab/6ezr/9PO0//Bo6n/xais/9HI
+ zv/Bqrb/xLG9/9DL1v/a3+3/5fj//8jF1v9zOFD/cio6/7ecpf/c4un/2+Tx/8e6zP+hbHf/n2p1/51n
+ d/95M0//YAoj/5lBE/+xWA7/vGYM/7tlC/+7ZQr/u2cL/7pkDv/BaA7/dyci/1YKKv/CmJD/8Pf0/+3/
+ //+unqz/ZyNB/1IBJf9pI0b/cDdc/2osUf9aDi//VwYg/4dUZv/T0NH/9f/+//P9/v/o9/b//v///5qD
+ m/9AABX/YiEs/864cv/w6Yj/6N2A/+zdff/r3Hf/6tp1/+jYcP/p1mv/59dr/+vRPf/vyQ//7ccR/+zE
+ D//rwgz/68AL/+u+Cf/qvQj/6bwI/+m7Cf/puQf/6bsI/+m7Cf/pugn/6bsJ/+m7Cf/puwn/6bsJ/+q8
+ Cv/pvgn/6r8K/+vBDP/rwwn/7Moq/+jRXf/o01z/6tRi/+nVbP/n02v/791p/9GzYv9aCir/Yg8x/1MF
+ I/+4pKb/6e3u/9PP1P+/n6T/xqis/9TJ0P/FsL3/w7C9/8Gvvv/Dtsb/0NDg/+L1/v/k+///oo2h/14Q
+ JP93PEn/vKqy/8vCz/+hbHr/nmV1/5VZbv9iETT/eyYb/6hQDv+5Ygz/vGYL/7xmDP+8Zwv/u2gN/71n
+ CP+ybzD/Ugop/5BXX//6+vD/8f3+//T///9/WHL/UgAZ/10KL/9yPmv/qJnB/7vD6f+7w+v/hV2C/0sA
+ Ev9tMED/6Ojn//H8///p/v7/nYig/1cONP+IUEj/5tqH//Lsjv/o34b/6uGC/+3cff/s3Hn/6tt3/+jZ
+ cv/p12z/6NZr/+vSS//wyhL/7cgS/+zFD//qwwz/68EL/+y+Cv/qvQj/6bwI/+m7Cf/puQf/6rsI/+m7
+ B//pugn/6boK/+m6Cf/puwn/6rwJ/+q9CP/qvwr/678L/+3BDf/sxAr/68w3/+fSXv/o1F7/6tVj/+rX
+ av/m1nv/7t51/9OzYf9aCyv/YxAy/1MFI/+4pqj/5evs/9rc4f/OvsX/xqmu/9LHzv/EsL3/xbTB/8O2
+ x//BscP/zMjX/9zt+f/b7fn/4PT8/6mInP9/QVf/YBct/3Y3TP+PV2j/onB7/4E/Wf9eCCT/lj8S/7FZ
+ Df+8Zwv/u2YL/7xnC/+8aAz/umMH/8Z6JP/Ap5H/RgAe/2UfR/91NV7/cC5W/3M2XP9lI1H/WQ0n/7yH
+ KP+ELRX/VwAn/3A/a/+tps7/eEVs/1MAHP+8np//+f///+Dx9P91TGz/UQAg/62GXv/y75H/7eiO/+jg
+ if/q4oj/6uCE/+vfiv/s3H//69x4/+nadP/q2G//6tZs/+rTWP/uyxf/7ckS/+vGEP/rww3/68EL/+q+
+ Cv/qvQj/6rwJ/+m6Cf/puQf/6bsH/+i7Bv/pugn/6boJ/+m6Cf/puwn/6rwI/+u+Cf/qvwr/68EM/+zD
+ Dv/txQ//7M9G/+jTXv/p1F//6dVl/+vXZ//o1Gn/7t9w/9G1Zv9ZDCv/YQ8w/1UEJP+5pqn/5ejs/9vi
+ 6P/i7PP/2Nrk/9nV3//HtcD/wrC9/8S3yP/Etcf/z8vb/9/x/P/c7vr/2eb4/7GWsP+nfY//oXuO/38/
+ WP9iEy//byY8/2QXOf90Hh7/o0sP/7VfDf+8Zgv/u2UL/7xnC/+9aQ//uV0A/9u2if/8+Hf/u5Mn/6Rx
+ Lf+lcSj/p3Mq/6ZzKP+jcC7/yqo8////Rv/87kX/zZQu/34iEv9ZCCv/Vggs/24sQP/s8e3/9f///4xq
+ hv9dDyX/3c2F//P1mf/o5JD/6eOO/+vkjf/o44n/6uKZ/+nhlv/s3n//7Nx5/+radf/q2HD/6ddr/+rW
+ Yf/uzB7/7ckR/+zGEf/rxA7/68EM/+q/Cv/rvgn/6r0I/+m7CP/puQf/6rsI/+m8B//puwj/6LsI/+m7
+ Cf/puwn/6rwJ/+u+Cf/qvwr/68EM/+vEDf/sxhP/69BO/+nTYP/q02L/6NZm/+rXaf/o1W3/8OBy/9K1
+ Z/9ZDCr/YRAw/1YDJP+4pan/5erw/9PP2P/Uztf/5vT6/+b0/P/c4+7/0MzZ/8e6y//DsMT/z8va/9/x
+ /f/e8Pz/1uHz/7ilwv+XX3P/n26A/6mAkv+fcIP/fT5U/14OMf9zHx//p1UR/79oCP+4YQr/u2YL/7xo
+ Df+8YgX/w3o1//fuqv/+6zH///w8///+QP///0P///9E////SP///0v///xL//vvSv/99VD///9Y//vt
+ Tv/Ffi3/WwEe/3U+Uv/z+/f/8P///3ZEY/9fESf/xbN2/+/qk//p45L/6+SR/+vjj//s44z/6uKI/+rg
+ gP/r3n7/7N17/+vbd//p2HL/6Nhs/+rWZv/uzif/7soS/+zHEf/sxA7/68EM/+u/Cv/rvgn/6r0H/+m8
+ B//puQf/6rsJ/+i7CP/puwn/6bsJ/+m7Cf/puwr/6rwJ/+u+Cf/rvwr/68IM/+zEDf/uxxb/6tJV/+rU
+ Yf/p1WP/6dZn/+rXav/o1W//7uFz/9K2af9ZCyv/YRAw/1MEJP+4pKj/5e7x/6R7iP+GP1b/j152/7Wl
+ t//k8fj/5/j//9vm8//Rzd3/09Hf/9/y+//f8vz/1d/w/8TB3P+hdYf/jU1b/5JXav+ebH//mWR7/2kb
+ PP9VBCz/ayQp/6VhGf/AcQv/vGII/7tkEP+4XQD/27aH//31a//76TX/+upD//rrQv/860T/++tI//ns
+ TP/57E7/+u9P//zzUP/98lP//O9Y//r1W////2f/k11R/1UKLv/k4N3//////7e2xv9TCi3/UwYj/8y5
+ ev/x7Zf/6OKP/+nikv/q447/6uKJ/+nhhf/r34H/6958/+rbd//p2XT/6dht/+rXaf/uzy7/8MoS/+3J
+ Ef/sxQ3/68IM/+vAC//rvgn/6r0J/+m7Cf/quQj/6rsI/+m7Cf/puwn/6bsJ/+m7Cf/puwr/6r0J/+u/
+ Cv/rwAv/7MEN/+zFDf/tyhz/6tNb/+rTYf/p1WT/6tZo/+rXbP/o1nD/7uF1/9K3af9aCyz/YQ8x/1QE
+ I/+4pKr/4+ru/5xtev9tHzv/XAAa/2cPG/93PFj/u66+/+b2/P/l+f//3Ov2/93u9//g9P3/097v/8TA
+ 3v+yn7b/j1Jf/41PYf+QVGT/aiBA/2AKIf9tGiX/UwQw/1MNLv+BRSP/tnIX/8FqCf+7ah7/7uOi//3r
+ QP/97UH//O5C//3wRf/98Uf//PFM//zyT//88k///fJT//3zVv/881v/+/Bd///9XP/c0oD/ZiNA/0sA
+ F/+IXGv/9vz6///////S2uP/Uwwz/6SAXf/29pv/5+OT/+rlnv/q44//6uOJ/+rhhf/r34L/6919/+vb
+ ef/p2nb/6Nlu/+rYa//t0DP/7soT/+3JEv/sxg7/68IM/+u/Cv/qvgn/6r0J/+m7CP/quQj/6rwI/+i6
+ Cf/puwn/6bsJ/+m8Cf/puwn/6r0K/+q/Cv/rwAv/7MIN/+zEDP/tyx//6dNb/+rTYP/q1Gb/6tdq/+rY
+ bv/o1nD/7+F3/9O3av9bCyz/YA8x/1UEI/+4pKr/4+rv/51wff9sHj3/bhci/6pRFv+VQBb/YAYa/3hB
+ Yf/Cu8n/6fv+/+Dy+v/f8fz/2uv3/8rN5v/Audj/ll9z/49QYf+RUmL/YBE5/3sgHf+oTAz/hS8Z/18L
+ J/9MAC3/XyIv/41RHf/Nomb///1///vmL//66Uf//e9H//zxRv/78Uv//PJQ//zzUP/981T//fNX//30
+ Wf/5813//vpb/+3ne/9fHjv/bzVS/5hmfv9QABn/bzhM/7mttv+ZhZn/TAIl/8axef/x8Zz/6OST/+vl
+ kv/r5JD/6uOL/+vhiP/s4IT/6959/+zbe//q2nf/6Nlv/+nYbf/t0Db/7soU/+zJFP/sxw7/7MMN/+vA
+ C//qvgn/6r0I/+m8B//qugj/6rsK/+m7Cf/puwf/6bwH/+m8B//qvQj/6r0I/+y/Cv/swAv/7MIN/+3F
+ DP/tyyL/6tRf/+jVZf/q1GX/69Zk/+rYb//n13H/7+J4/9K4bP9ZCyz/YQ8x/1MEIv+5pKr/4unu/59x
+ fP9sIDv/bhQg/6hQEv/txiH/5bAg/5Q8EP9fBRz/h1ds/9zn6//h8/3/4PL6/9/z+v/a7vz/ro+i/45K
+ W/+OTWD/Xw40/4ApGf+uVQ3/umcK/6ZODv96Jhz/Vgcq/0oGLf9sOEj/vqNp//jyY///+Uf/++tB//nt
+ S//78k///fNR//30U//89Ff//PVb//v1Xv/79Vv//f10/3pCTf9jITv/4u7u/9Lh9/+tjaz/cjNM/1IH
+ KP9XCir/rIZn/+7wnf/q5pf/6eaT/+rljv/s44n/6eOE/+vghv/s34P/695+/+rbe//p2nf/6tlw/+rY
+ bv/t0Dn/7csX/+7JFf/txhD/7MMO/+zAC//rvgn/6r0I/+q9B//pugj/6rwI/+m8CP/puwj/6bwH/+q9
+ B//qvQj/674J/+y/Cv/swAv/7MMO/+3FDP/tyyT/6tRf/+jXcP/o3ZP/6NuL/+rYb//n2HL/7+J5/9G4
+ bP9YDCv/YQ8x/1QEI/+4pav/4+zx/6B3f/9tITz/bRcf/6lSEP/ouhv//+gh//3mJf/jrCT/chgO/4tr
+ eP/r////3u/4/9/x+v/k/P//0tzl/51qfP+MUGP/Xgsu/4syFv+xWA3/uWYL/71rCf+8Zgr/oEYS/3Eb
+ Hf9OABv/SQMt/3RBUv/Fr3X/+/hr///7Tv/67kr/+/BU//31V//99ln//fVf//rxYP///2z/qoZt/1AB
+ J//WztH/9v///9jr9v/H3v//w67A/7eNjv/YyZX/9vWe/+nnlf/s5pn/6uie/+vpqP/s6rP/6+u7/+vs
+ vv/q5Jv/699//+vdff/q23f/6dpx/+nYb//u0Tr/7ssW/+3JFv/txxD/7MMN/+zADP/svwr/6r0J/+m8
+ CP/ouQn/6bwH/+m8B//puwn/6bwI/+q9CP/qvQj/674J/+y/Cv/swQz/7MMO/+3GDf/syyX/6dRg/+nV
+ Y//p2W3/59p8/+jZcP/o2HP/7+J5/9G5bf9ZDCv/YQ8x/1QEJP+3pKr/5vL5/6WHkP9rHzn/bBgf/6lU
+ Ef/swBz//N4h//TZJP///y//ro9T/2o4T//q/v//3/H5/+Dz+//h8/r/4PX8/66QoP+TWWz/Xgss/5E4
+ Ff+0Wwz/vGgL/7xmDP+9aA3/v2kF/8FyI/+zeUv/dB0Z/0cAIf9FBi//eUpd/828fv/+/XH///pX//vv
+ U//88l7/+/Nl///9Y//k2YD/UgIs/6eFj//6////6Pb3/+T1+v/D1vP/uJuu/93Pmv/v8KP/5+ms/+zt
+ t//s7sD/7e/D/+vvv//s67T/6+im/+vllv/q4Yn/6uCC/+vcfP/r23j/6Nlx/+jYb//t0Tr/78oV/+zK
+ Fv/sxxD/7cMM/+zADP/rvwn/6r0J/+m7Cv/nuAn/6boJ/+m7Cf/puwn/6bsJ/+q8Cf/qvQj/6r4J/+q/
+ Cv/rwAv/7MMO/+3GDf/tyyT/6NRg/+fVZf/o12j/6Nhq/+fZcf/o13T/8ON6/9K5bv9bDCv/Yg8x/1MD
+ JP+2par/6PT7/8O7w/9pITn/bxkf/6dSD//pvRz//uEj//faJf//8zL/q4hZ/2w5UP/p/f//3/H5/+Dz
+ +v/g8vn/5Pz//8O7x/+MT2X/YAor/5Q7Ff+zXA3/vWgM/71oDP/Aag7/u2AA/8uRVf///5T/784s/7hr
+ Kv9zFx//RgAh/0kINf+EVWP/18aE////dv/++l//+u5c////eP+MXV//ZR4+//P39v/s+/3/6/39/+r/
+ ///H3fj/t6Cy/97Snf/r7qP/6eqn/+rro//q6J7/6uaX/+rmj//q5Iv/6eOJ/+viif/p4Yb/7N+C/+ze
+ ff/q23n/6dly/+nYcP/t0Tj/7swX/+vKFf/sxhD/7cMO/+vAC//rvwn/670K/+m7Cv/puQj/6rsJ/+m8
+ CP/puwn/6bsJ/+q8Cv/rvgn/678K/+rAC//swAv/7MMO/+3FDf/uyyD/6NRe/+jVZf/p12n/6tht/+jZ
+ cf/o13P/8ON5/9K5bv9bDCv/Yg4x/1IDI/+3p6z/4+vy/+Lt9f+BVmn/XwUQ/7FbGP/y0CD//N0f//XW
+ Kv//9jb/rYhZ/204Uf/q/f//3/H4/+H0+v/h8/r/4fj+/9fn7v+NVmz/Xwcl/5c/FP+0XQ3/vWkM/71o
+ DP/Baw7/u18A/9Wqbf/89Xf//PQ/////TP/wzj3/tmUp/20SIP9CACT/TQ04/41gZf/bzoj///96/+bd
+ i/9PAS3/sZeg//r////r/P7/z8vS/8m+yP/H2/f/t6S3/9vNm//s7qH/6Oib/+npm//q553/6uec/+rn
+ l//r5ZP/6+OP/+vijP/q4Yj/7N+C/+vefv/q23n/6dly/+nYbv/u0Db/7cwZ/+zKFf/sxg//7MMN/+zA
+ C//svwr/6r0J/+m7Cv/quQf/6rwI/+m9B//puwn/6bsJ/+q8Cf/qvQn/6r4J/+q/Cv/rwAv/7MIN/+7F
+ Dv/uyhv/6dNc/+jVZv/p1mj/69dt/+nZcf/o13j/7+J6/9K5bf9bDCv/Yg8w/1MFI/+4p6z/4+vy/+Hq
+ 8f/GyNX/Yh04/2YQG/+9m1z/++tR//3mL///8zH/rYVc/283Uv/r/f//3/L5/+H0+//h9Pv/3/L5/+X/
+ //+bdIf/XQEe/5lCFP+1Xgv/vWkL/71oDf/Baw7/u18A/929gv//+Gb/+uxC//rvTv/9/FP///9W/+/I
+ Qf+uWin/Zw8g/0EAJf9QDDn/oH14/5hrbP9fGzv/7O/u/+/6+//v////09Tb/62Hk/+2mqj/tJet/9jJ
+ nP/r76H/5+mj/+npnv/p5pf/6eWS/+vlkP/r5JH/7eSQ/+vjjf/q4Yr/6+CD/+rdf//q23n/6dlz/+nX
+ bP/vzzH/7cwa/+zKFf/sxw//7MMN/+zAC//rvwn/6r0J/+m7Cv/quwb/6rwI/+m8B//qvQf/6rwJ/+m7
+ Cf/qvQj/6r4I/+u/Cv/swQr/7MMN/+3FD//tyhb/6tNX/+jVZv/o1mn/6Ndv/+jZcv/n2Xv/7+J7/9K5
+ bv9bDCv/Yg4x/1MFIv+5qKz/5+30/9rh6f/i7vf/0dXm/4xnhv9WCTL/dDE6/8enZv///2P/spNj/281
+ U//r/P//3/L5/+L0+v/h9Pv/3/H4/+n///+upLL/WgAa/5hCFf+zXA7/vWgL/7xpDf++agz/u2EF/+XL
+ kP//913//PBF//zyT//88VL//PJV//79Xf///lz/6cJF/6ZYKP9iDCD/SQAm/1IAJ/95PlX/8PTx//z/
+ ///u+vz/8P///9Xf7v+8qLr/y6mV/+fjof/q66H/6uyz/+zwx//r8tD/6vDI/+jqsP/q5Zj/6uKK/+zf
+ hf/r34T/69+D/+zdf//q23j/6th0/+nXaP/vzyv/7cwb/+vJFP/rxhD/7cMN/+zAC//rvwn/670K/+m7
+ Cf/qugf/6rwI/+m8B//pvAj/6bwJ/+m7Cf/pvAn/6r0J/+y/Cv/swAv/7MMN/+zFD//tyRL/7NJP/+jU
+ Z//o1mj/6ddu/+nacf/n2HL/7+N4/9K5bf9bDCv/YQ4x/1MEJP+5pqv/5+3y/9zj7P/d5O//4ez3/+j5
+ ///Mz+L/hFx7/1ACK/+CTE7/gVBS/3E3TP/q/v//3/H5/+L0+//h9fv/4PP5/+b8///K1d7/Ww4n/5I7
+ FP+yWw3/vWcM/7xoDP++agz/umUL/+jUmP//9lf//PFJ//3zUf/+81T//vNZ//zyW//79GD//v5o///8
+ ZP/owEb/lkwx/1oJMv9eGDr/ZyQ8/7WdpP/0+vn/7vn7/+f5/f/G3Pn/y8C6/9/Omf/q66D/6eqg/+jr
+ pP/p7bL/6/LM/+v48//q+PT/6fHa/+rrvf/q5Jz/69+C/+vcfP/q23n/6tl2/+nWYf/u0CX/7cwc/+zJ
+ FP/sxhD/7MMN/+vAC//rvgn/670K/+m7Cf/qugj/67wJ/+m8B//puwn/6bsJ/+m7Cf/quwr/670K/+y/
+ Cv/swQz/7MMN/+3FEP/tyQ3/7NFD/+jUaP/p1mf/6tds/+jZb//o13L/7+N2/9G5bP9cDCv/YhAy/1IC
+ JP+8qq7/5uzx/9rg6v/e5/H/3ujy/9zn8//j8fz/5/z//8XK3f+BVXL/YiE8/7ajrv/n/f//3/D4/+Dz
+ +v/h9fr/4fX6/+D0+f/k/P//cDxU/3EXEP+1XhH/v2YK/7plDP+9aQv/u2gQ/+3dmf//9lT//PJM//30
+ Uv/+81b//fRb//32Xf/99WH//fJk//v0Zv///3L/yLB+/1MIK/+6zNz/e1F6/0oAD/+ymZ//+////+/+
+ /P/X6/z/v9f//723zP/ayJz/6uyi/+jpof/o6Jz/5uaT/+nutf/t+vj/6fv//+j7///o+fr/5fTl/+ng
+ kf/q2nT/6Np3/+vUV//uziL/7c0c/+zJFP/sxQ//68IN/+u/Cv/rvgn/6r0J/+q8Cv/quQn/6rwH/+m8
+ B//puwn/6rwK/+m7Cf/quwr/6r0K/+q/Cv/rwAv/7MMN/+zFD//vxwz/7M81/+nUZ//p1mX/6ddt/+nY
+ b//o13b/7+F5/9K5av9cDSz/Yg8y/1IAI/+Yb3n/7Pb1/+Lt8//e5O7/3ufx/97r9f/d6vb/3Or2/+T3
+ /v/p/f//5fT9/+X4/f/g8Pr/4vT7/+L1+//i8/v/4vT7/+Hz+f/o////wcPO/1kHJf92HhX/uHMa/8Fv
+ Cv+5Ygj/u2gZ/+7gnP/+9FT//fJO//70U//+9Ff//fVc//z2X//99mL//fZo//vzZf///33/lWtr/2cm
+ P//3////jn2d/18HLf+Va37/z8jQ/8zI0v/O2+z/w+D//7/J5P/Kr5//6+ug/+fpn//p6Z7/6eif/+nn
+ mP/q66b/6vjy/+n6/v/m+/z/5Pv//+jjnf/p2nP/59h2/+zSSv/tzSL/7Msb/+zJEv/rxQ//68IN/+q/
+ Cv/rvgn/674J/+m8B//qugn/670I/+m8B//qvAj/6rwJ/+m7Cf/qvAn/6r0J/+q/Cv/rwAv/68EM/+3E
+ D//uxg3/7c4l/+rUY//o1Wb/6dhs/+nbe//m2X7/7997/9K5av9bDS3/Xw0w/1wNMP9RBB7/eUNR/8K2
+ u//o8/n/5fH7/9vn8v/c6vX/3u35/9zs+P/d7fn/4PH7/9/v+P/h8/v/3e74/9/u9//j9/z/4vX6/+L1
+ +v/g8/n/6P///76+zP9pJUr/XA0b/55eI//CeBT/wWcW/+/fnf/88lb//PFT//30Vv/99lr//fZd//32
+ Yv/99mX//fZq//z3Zv///4r/cTtL/4dWZ//+////gGuO/3AqPP/RvaP/x7Ca/76enP+4mKT/vKq8/7uj
+ sv/Uwp//7O2h/+fpn//n55z/6eaY/+vmm//q5pP/6u60/+r48v/n9Ob/6Oqz/+zfgf/p33v/5+St/+3S
+ SP/tzSL/7Msb/+zJEv/rxg7/7MIM/+q/Cv/rvgn/674J/+m8B//qugj/6rwJ/+m8B//pvAf/6bwI/+m7
+ Cf/pvAf/6r4J/+q/Cv/rwAv/7MEM/+7DDv/uxg//78sY/+nUWP/n1Wf/6tZq/+rZbv/m2Hf/7eB4/9G3
+ a/9ZDCz/Xw0w/1gJK/+SXWn/dCs//1MAF/+ASlj/x77F/+b3/f/i9P7/2+n2/9zs9//f7/v/3/D6/+Hx
+ +v/h9f3/1OPx/8jG3//W3+7/4fP5/+X4/P/j9/v/3vL5/+f+///f8vj/kXSO/1AEKP9wLCD/s3U4//ry
+ rP//+VD/+e1R//vzWP/891v//Pde//32ZP/992f//fZt//77a//295L/Whw5/6WDjv//////fGqM/3c3
+ Pf/y+bD/7O+n/+jsqP/e2KT/1MGf/9nIoP/r6KL/6eqh/+jpn//q6qj/6eip/+rlmv/r5ZT/6+WN/+rl
+ l//s4o//6d9+/+nefv/p3Xr/6dx3/+7SPP/s0CT/7Mwa/+zIEv/rxg7/7MIM/+q/Cv/rvgn/6r0I/+m8
+ B//ouQn/6rwI/+m7Cf/pvAf/6bwI/+m7Cf/qvQj/6r0I/+u+Cf/qvwr/7MEM/+3DDf/txhH/8MkR/+rS
+ Rv/o1Gj/6dVo/+jXbf/m1m//7eF2/9G3av9YDCv/Xg8x/1UFKP+qkJb/zLa5/6uCh/9xKjv/VgEZ/4JP
+ Xv/Iwcv/6Pr//+P2/f/c7Pb/3u73/+Dx+v/h9f3/1uXy/8XC3v/ExOL/yc7o/9bi8v/h8vr/5Pj8/+Dz
+ +f/k+Pv/6////8PG1/9xQGX/Uwcn/5BgWf/o3n3///9m//z1U//58Vv//PZh//72Zf/9+Gr//PZv////
+ b//o45L/UQwv/76prv/+////fGqM/3Y3Of/t9LD/5euv/+jtrv/s76f/7PGh/+vuof/p6qP/6eqh/+rp
+ n//p6qD/6Omk/+nmnP/r5JD/6+OR/+rjiv/q4Yf/6OCF/+rdff/p23r/7NdX/+3RMP/szyT/7MwZ/+zJ
+ Ev/rxQ//68IM/+q/Cv/rvgn/6r0I/+q9CP/ougj/6rwJ/+m7Cf/puwj/6bwJ/+m7Cf/pvAj/6r0I/+u+
+ Cf/qvwr/68AL/+3CDf/sxRD/7scP/+/QMP/o1mT/6dZl/+nXbP/n1nD/7uJ2/9C3av9YDCr/YBAx/1QF
+ KP+pjJD/uqGr/3c3Tf+ykJn/qX6H/28gM/9VAxv/hlNk/8vHzv/o/f//4/j+/93u9v/g9Pr/1+Py/8bE
+ 3//Iy+b/xsro/8PI6v/I0e7/1+T1/+T0+v/m+Pz/4fX6/+j+/v/m+///sqrA/2MmTP9VCCv/oHpi//Hs
+ f////2n//PZc//rzZf/89m//+/Zy////c//XyYv/TQYr/9HGx//7////e2uM/3U3Ov/w9rD/6eys/+ru
+ uf/p8MT/6e/F/+jss//o6KD/6eaY/+vnmv/q55z/6ueZ/+nmlv/r5JL/7OON/+rji//p4If/6d6B/+nc
+ e//p2XL/7dVG/+3QLf/szSL/7MoY/+zHE//rxQ//68EM/+q/Cv/rvgn/6r0I/+m8B//nuQf/6bsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/pvAj/6r0I/+u+Cf/qvwr/68AL/+zCDP/sxQ//7cYQ/+7MG//q1Fr/6NZm/+rW
+ av/o1m//7uBz/9bAav9cECv/YhAx/1IBJf+jhIz/r5Sc/2EKH/9sMUH/yL3D/7+os/+idYH/axsw/1UD
+ Gv+JV2j/zszV/+r////l+///1+Xy/8nE3//Gx+X/yMzp/8jP7f/Fzu//w8zw/8nU8//Z5vf/5fb7/+X3
+ +v/k9/v/6v///+H3/P+klq3/WhdB/1gSMv+wkWz/+faD////bf/89Wv/9+9w////e/+2mXf/Uw0x/+bn
+ 5f/3////fWyO/3Y5PP/x9rD/6uqm/+rrp//p7an/6fC6/+vz0P/r9eD/6fDN/+nprf/p5Jn/6+OQ/+rk
+ j//s5JH/7eSN/+riif/p34X/691//+ncff/n2nj/7NJA/+zPK//szB//7MoV/+3HEf/sxA7/6sEL/+q+
+ Cv/rvgn/6r0I/+m8B//ougj/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/qvAr/6rwJ/+u+Cf/qvwr/68AK/+zC
+ C//sxA7/7scS/+7KEP/r0UD/59Vo/+nWZ//q123/6dhw/+rcdP9xLzb/Wgos/1wKLP90OlL/0MjL/4JO
+ Y/9fIjX/way0/2snR/+BTWT/xbe//5xufP9kFyz/VwUe/4lebv/P0dj/6f///+H0+//P1+v/xsro/8fM
+ 7P/H0PH/x9Hy/8bQ9P/Cz/X/x9b2/9jo+f/l9vv/5vf6/+X5/P/u////3u/2/5iAmv9RCjf/ZSM4/8Wr
+ d//9/Yj///99//r5jf9rJj3/gE1k//v////x////fmuO/3Y5O//x9rH/6uqo/+rrp//p6qj/6eqj/+np
+ oP/p8Ln/6vr5/+r6/f/p9ef/6e7G/+vlov/q4Yn/69+D/+zfhP/s3oP/6918/+rceP/q23T/6NRT/+zO
+ KP/szRz/7ckT/+3GD//qww3/6sAL/+q+Cf/qvgj/6r0J/+m7Cf/ouwf/6bwJ/+m7Cv/puwn/6bsJ/+q8
+ Cv/qvAn/6r0J/+u+Cf/rvwr/68AK/+vCCv/sxAz/7cYQ/+7IEP/tzyP/6NVi/+nVaP/q12z/59Vw//Dl
+ d/++nVz/VAIj/18PMf9YCSv/i19z/9TKzP/QvsH/yrK9/3Y4U/9TBxv/qI+b/9LI1f/Drbn/nWx6/2QW
+ Lf9WByD/imNz/9DX3P/u////4vT7/9DY7v/Gzu3/xs/y/8jT9P/G1fb/w9P2/8DR9//G2vf/2Or5/+b3
+ +//n+Pr/6fv8/+/////V5O3/hmeE/0wDLP90M0H/nHBa/2slN/9YDy//2NPV//X////w////gGyP/3c5
+ O//w9q//6uqo/+rrpv/p66f/6eqm/+rqpv/q6Z//7PXb/+z7///q+/3/6Pz//+j6+v/m8tz/6eix/+nf
+ i//q23r/69t4/+zab//u1kP/7NIw/+3OJf/tzBj/7cgR/+3FD//rwg3/6sAL/+m+Cf/rvgn/6r0I/+m7
+ CP/ouwb/6LsH/+m7Cf/puwn/6bsJ/+m7Cf/qvAj/6r0I/+q9CP/rvgn/68AK/+vCCv/rxAr/7MUP/+7H
+ Ev/vzBL/69RJ/+jWaf/p1mv/6dhx/+fXc//x5nj/tpJZ/2EWLv9UAyv/UgEl/2ooQ/+mipX/2tDU/8Cn
+ tf9tNkz/tZqo/8q4xf/GuMb/zcLR/8avv/+bZnf/YREq/1MKIf+Oa3n/1t7i//D////i9Pz/z9vy/8fR
+ 8v/E0vX/xtb2/8bX9//C1fn/v9T5/8ba+f/a6/r/6ff7/+r4+f/s/P3/9P///8zT3f+MZX//dDVY/4pa
+ cv/Z0tb/9v///+329v/x////f2yP/3c4O//w9q7/6eqk/+rspv/q7KX/6eqk/+rqpf/q6p7/7fXV/+z8
+ ///q+/r/6Pv6/+f7+//l+///6fDM/+rqsP/n5Jr/6dx+/+vXW//u0jf/69Es/+zNH//tyhf/7McQ/+vE
+ Df/swQz/678K/+m+Cf/rvgn/6r0I/+m8B//nugj/6bsI/+m8B//puwn/6bsI/+m8B//qvQj/6r0J/+q9
+ C//rvgn/6r8K/+vBC//twwv/7MQO/+3HEf/wyRL/7s4n/+jVZP/q1mn/6tdu/+nYc//o13P/8+h6/97K
+ c/+aalD/Yhgz/08AJv9RASL/bS9L/7CYof/d2t7/1MbO/8Ouu//Fs8D/x7fH/8m+0P/Nxdn/xq/B/5pm
+ e/9fDyn/Vw0m/5Fxfv/Y4eP/8f///+P1/P/Q3fT/w9P1/8PT+P/E1/j/xdj6/8LX+/+/1fr/yd36/97s
+ +v/q9/r/6/n6//L+///4////9/////r////0/f7/7vr7//D5+P/x////f22O/3Y3Of/v97H/6eus/+vq
+ n//p66P/6uqh/+rpov/o6Jz/7O+2/+77/v/q+/z/6Pv8/+b7/f/m/P//7OWi/+3he//p4of/6d5+/+vY
+ W//r0j//7c4p/+3MHP/tyRX/7cYQ/+vDDf/qwAv/678K/+u+Cf/qvQj/6r0J/+m7Cf/ouQf/6rwI/+m8
+ B//puwn/6bsJ/+m8CP/qvAn/6rwJ/+q8Cv/rvgn/674J/+zAC//swgv/7MMN/+3GEP/uyBP/8MoU/+zS
+ Rv/o1mv/6thq/+nYcv/o2nX/6dd0/+/gd//37X//2sd2/5ZmUf9fFTP/UgAl/1EBI/9xNU7/rpih/9jR
+ 1f/TxtD/xbPF/8a4yP/HvM7/ycLX/8/I3//CrsX/lWB5/10NKf9WDyX/lneB/93m5//y////4vX8/87d
+ 9v/D1fb/wdb6/8XY+//F2vv/wtj7/8DX+v/K3vr/3O76/+z4+v/u+Pn/7Pj7/+z3+v/u+fn/8Pz9/+/4
+ +v/x////fm6O/3Y4Ov/x9qj/6+6+/+vrsv/q6Zr/6uqg/+rqnf/o6Z//6ema/+vzzv/q/P//6fv6/+f5
+ 8//o8dX/7OGM/+vfgP/p3Hf/6dph/+zVSP/q0zr/7c8o/+3MGv/tyRP/7cUP/+vDDf/qwAv/674J/+u+
+ Cf/qvQn/6r0J/+q7CP/qugj/6rwI/+m8B//puwn/6bsJ/+m7Cf/pvAr/6rwK/+q8Cv/rvgn/7L8K/+3A
+ Cv/swQv/7MMM/+zED//uxxD/78gU/+7QIv/q1l//6dZq/+nZb//r2nb/6dyB/+fchv/p2Hn/8eF5//bt
+ h//Vw3n/kF9R/1wRMf9RACT/UwQm/3c+VP+0oqj/29bb/9PJ1v/Gt8r/x7zO/8nA1f/Ix93/zMvl/8Cu
+ yv+SXHr/Wgsn/1oTKP+cfof/4urp//X////i9Pz/y972/8LW+f/D2Pr/xdv6/8Tb+v/C2vv/wdn6/8zg
+ +v/f7/v/7/r8//H9/f/v+/v/8Pz9/+/5+//y////f2+P/3U4O//y9qj/6Oyh/+vxyv/q7LX/6+eX/+zo
+ nv/r55z/6uab/+vol//q7LH/6uut/+vmlP/r44j/6+CA/+veff/q3Gz/7ddO/+3UOf/t0in/7c0f/+zL
+ Fv/txxD/7cQP/+rCDP/qwAr/678K/+q9CP/qvAr/67wJ/+u8Bv/pugj/6rwK/+m7Cf/puwn/6bsJ/+m7
+ Cf/qvAr/6rwK/+q8Cf/qvQj/674J/+y/Cv/swQr/68IM/+vEDv/txg//7cgU/+/MFP/u0jr/59Zq/+nX
+ av/r2nD/6dx5/+nfhv/o56r/6OKo/+fZgP/v5ID/9O+L/8+9e/+KVlH/XA0x/1EAI/9VBSj/e0Nc/7mo
+ sv/a2eH/0crc/8W80v/Gv9X/x8Lb/8nK5P/Lz+z/vq3M/41Zef9ZByX/XRYq/6CDjP/j7e3/9v///+L1
+ /P/M3/b/wdf5/8LZ+//F2/v/xdz7/8La/P/B2vz/zOL7/+Hx+v/w/Pv/8Pz8/+/5+f/z////gG6O/3U5
+ Of/x9qj/6eqg/+jtoP/q8sv/6+u0/+vmk//s55n/6+aX/+rmlv/r5Yz/6eOL/+njif/p4ob/6t9+/+va
+ cf/q2Fz/7NVC/+7TMv/vzyT/7c0a/+3KEv/txw//7MUL/+zCCv/qvwr/6r4K/+u9C//qvAr/6bsJ/+m7
+ Cf/ougj/6bwJ/+m8CP/puwn/6bsK/+m7Cv/pvAj/6bwJ/+q8Cv/qvQj/6r0I/+u/Cf/rwAr/68EL/+zD
+ DP/txQ7/7cgS/+7KFv/wzRr/6tVQ/+jVbf/q2G7/6tp2/+vbbf/p56H/5/b//+fw4//m5rn/6N6R//Ho
+ h//z743/zbh7/4ZRT/9ZCy7/UAAk/1cIKv9/TWH/va+4/9vd5//PzOD/xL7Y/8fD2//HxuD/yc7q/8vQ
+ 8P+6rND/iVR2/1cGJP9hGS3/p4yS/+jx8P/1////4fT7/8zg9//C2vn/wtr8/8bc/P/G3fz/wNr8/8jh
+ +v/w+/v/8fz9/+/5+v/y////f26O/3Y6Ov/y96j/6eqg/+nrn//q7J//6fHH/+nqrf/r5pL/7OaU/+vl
+ kf/s5JD/6+KO/+rhif/p333/6eWe/+rhkv/v0kP/7dM9/+7QKf/vzh3/7MwU/+vJD//sxQ7/68ML/+vB
+ Cv/rvwr/674K/+u9Cv/qvQn/6rwJ/+i6Cf/ougj/6rwJ/+m8B//puwn/6bsK/+m6Cv/pvAf/6bwI/+q8
+ Cv/qvQj/670J/+u+Cf/qvgn/68AK/+3DC//sxA3/7cYP/+3IEv/vyxX/8NIj/+nWX//o1m3/7Ndx/+ra
+ df/q337/6fHY/+f4///n9///5vPy/+Xry//o4qD/8uyM//PtkP/HsHr/gElL/1cIL/9QACT/Vwss/4NT
+ aP/AtsH/2d/s/87N5v/Fwt7/yMbh/8bJ5v/I0fD/y9P0/7asz/+DUHP/VwUi/2UdMf+rk5j/7PTy//n/
+ ///j9Pv/zeH4/8Pb+v/D2/z/xNv8/8vj+//w+/v/8fz9/+74+v/x////fm6O/3U5Of/y9af/6umf/+rq
+ n//q6Z3/6eqb/+jtt//q55z/7eWO/+zkj//q5Iv/6uKJ/+nhhf/s3Xf/6eio/+P3/P/s2WX/7tAt/+3P
+ Jf/uzRj/7MoS/+3GD//swwz/68EL/+u/C//svwr/674J/+q9CP/qvQj/6bwI/+i6CP/qugj/6rsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/qvQj/6r0I/+u+Cf/rvgn/68AK/+vCCv/rwwz/7MQO/+7H
+ Ef/vyRX/8MwY/+3SMP/o1mb/69Zt/+nZc//q23T/695//+vqt//o9fX/5fb8/+X3///k9vv/5e3a/+jm
+ rP/z75X/8e+X/8Kqef98QUf/VgYs/04AJf9ZDjD/h1tw/8W+yf/Z4fP/zM7q/8XF4//HyOf/xszr/8nU
+ 9v/I1vr/s6rQ/4JOcv9VAyL/ZyI2/7CZoP/t9/X/+P///+P0+//O4vf/wtr6/8zh+f/w+/r/7fz7/+35
+ +P/x////f22O/3Y4OP/x86T/6uid/+ronf/r6Jr/6+eY/+rnl//o55r/6+SO/+vjiv/q4Yn/6uGH/+ve
+ gv/t23H/6uWb/+L5///o5qr/780g/+7OH//syxT/7ckP/+7FDf/swwv/68EL/+vAC//qvwr/6r4J/+q9
+ CP/qvAr/6rwK/+m7Cf/pugf/6rwJ/+m7Cf/puwn/6bsJ/+m7Cf/puwr/6bsK/+m7Cf/qvAn/6r0J/+u9
+ Cv/rvgr/674L/+vBCf/swwr/7MQL/+3GDv/vyBH/78oY//HNF//t1Dv/59Zr/+nXbf/q2nL/7Nt2/+ze
+ df/q8d3/5vb//+b29v/k9/n/4/j9/+T6///l7dL/6uCH//Tzl//v7Zf/u6F0/3g5Rv9WBCz/UAAk/1wS
+ MP+NZXX/x8XO/9ni9//Lz+//xMfo/8bM7P/Gz/D/yNj6/8nY/f+xrND/f0pv/1YCIv9rJjv/tKGo//L6
+ +f/5////4vP6/9bn9v/u+vv/7/z8/+75+f/w////fmyO/3U3Nv/w86L/6uaX/+znmP/s6Jb/7OeV/+3m
+ lP/r5Y//6uOM/+vhiP/r34b/6d+B/+reff/u11r/7OGC/+L6///n45r/8MwZ/+3NGP/tyRP/7McO/+zD
+ Df/rwQv/68AK/+u/Cv/qvgn/6r4I/+q9CP/quwr/6rsK/+i7CP/ouwb/6rwJ/+m7Cf/puwn/6rwK/+q7
+ Cv/pugr/6bsK/+m7Cf/qvAr/6rwK/+q8Cv/qvAv/7L4L/+3ACf/swQr/7MMK/+zFC//uxhD/7skT/+7L
+ GP/wzhr/7NRF/+fXbf/p2G7/6tl1/+zbdP/s557/5/j7/+b4/v/k+f//5/To/+vsuf/o8tb/6eu5/+jh
+ l//o45H/8/Oa/+3pl/+3mXL/dTRD/1QELP9RACT/YBYz/5JufP/KzNf/1+X6/8nS8//Dye7/xc7v/8bR
+ 8//G2/3/xtv//6+r0P98SW7/VQEi/24sQ/+6qq//8/z6//n////u+vv/7Pj5/+34+v/w////fW2P/3Y2
+ NP/x9KX/6Oak/+vlk//s5pT/7OWR/+zlkP/r5I3/6uKK/+vfh//s3oH/6t99/+zaZf/u1kv/79lQ/+jj
+ i//s00D/7s0Z/+7LFf/uxxH/7MUN/+vCDf/rwAz/7L8K/+y+Cf/rvgn/6r0I/+q9CP/qvAn/6rwK/+i6
+ CP/ougj/6rwJ/+m8B//qvAn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6rwK/+q8Cv/qvQj/674I/+y/
+ Cv/rwAv/68IM/+vEDP/txQ7/7ccR/+7JFP/vyxr/8M4c/+vWS//o1nH/6ddu/+nadv/t3HT/7eei/+nz
+ 4P/o8+f/6+er/+zhgv/q5Iz/6umj/+nmo//q45D/6OKS/+nllP/z85z/6eaX/7KSb/9wLUH/VAEs/1EA
+ I/9iGjf/lnaD/8zS3f/W5/7/yNP2/8PM8P/F0PT/w9P3/8bd/v/F3P//rqrR/3lFav9WASP/cjJI/76v
+ tf/0/Pv/+f///+z3+P/u////fW2P/3c4N//y85n/6emp/+rkmP/s5I//6+SP/+rjjP/r4Yr/6uCH/+ne
+ gv/r3YD/7dpk/+3YR//s1j//7dM2/+7QIP/uzhz/7cwW/+7IEv/txg//7MIM/+zBDP/rwAv/678K/+u+
+ Cf/rvQv/670L/+q8Cv/qvAr/6bsJ/+m7Cf/pugf/6rwI/+q8Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/qvAr/6rwK/+q8Cf/qvQn/674J/+u+Cf/svwr/68AL/+vCDP/sxA3/7MUP/+3HEf/tyhP/7cwZ/+/Q
+ Hf/s1kn/59Zv/+nYb//r2nX/6ttz/+reev/s34D/6+B//+rhiP/r4on/6+OI/+njjP/p5JD/6eSQ/+nl
+ lP/o4Zf/6uaU//P2nP/m4pT/q4hr/2wnPv9SASv/UgAk/2UhOf+bfIn/ztbg/9bo///G1Pr/wc/1/8LT
+ 9v/D1Pn/xd7//8Xc//+qqM7/dkBm/1QAIv91N0v/xbe4//X8+f/4////fG2P/3Y5N//08Zj/6+aV/+nm
+ mv/s5I3/6uOK/+nhiP/r34f/6t6C/+ndf//q22X/8NZF/+3VO//u0zD/7tAp/+7OIv/uyxn/7skS/+zH
+ EP/sxA7/68IM/+vAC//rvwr/674J/+u+Cf/qvAr/6rwK/+q8Cv/qvAr/6bsK/+m7Cf/quwX/6rwI/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6rwK/+q8Cv/qvAr/670K/+u+Cf/rvgn/678K/+vB
+ DP/rwwz/7MQN/+zFD//tyBD/78oV/+7MGP/yzxz/7NVG/+fXbP/q12//6tl1/+vbd//r3Hv/6t1//+rf
+ gf/q4YT/6uKI/+rji//r44z/6+OK/+zlnf/s5Jb/6uWS/+fik//r55b/9vWd/+PdkP+lgWX/aSI8/1IA
+ Kv9RACX/ZyI//52Ej//P3Of/0+n//8TU+v+/0Pb/w9P4/8PV+v/G3///xNz//6qkzP90PmP/VAMh/3o8
+ TP/IztL/fW+S/3c4Nf/z8Zb/6+SQ/+rkjv/s5Iv/6+KJ/+rfhv/r34L/6d1+/+3aYf/v10L/7tQ7/+3T
+ L//u0SX/7s4f/+3MGf/uyhL/7scP/+zFDv/rww3/6sEL/+vAC//svwr/674J/+u+Cf/qvAr/6rwK/+q8
+ Cv/qvAr/6rwK/+m7Cf/qugj/67wK/+m7Cf/puwn/6bsK/+m6Cv/puwn/6bsJ/+m7Cf/puwn/6rwK/+q8
+ Cv/qvAr/6r0J/+q9CP/rvgn/678K/+rAC//swQz/7MMN/+vEDf/sxg7/7cgR/+7KFP/tzBj/788a/+zU
+ P//o1mn/6tZv/+nZcv/r23f/69t6/+3dff/r33//6uCC/+vhhv/q4of/6+SL/+zknP/q44v/6+SP/+zl
+ kv/q45H/6eGR/+3olv/09Zv/4deO/6J5Y/9mHzv/UQAr/1EAJv9qKEL/ooyX/9Hf6//S6f//wdT7/7/R
+ +P/D1Pj/w9f7/8Xg///E3f3/q6TM/3E3X/9VCyj/Vw81/4FEQv/w7JH/6+OM/+vjiv/q4Yn/6t+F/+rf
+ gv/r3Xr/7dlZ/+7XP//v1Df/7tMs/+3QI//tzhz/7cwW/+vKE//tyA//7MUN/+vDDP/rwAv/7MAL/+y/
+ Cv/rvgn/670K/+u9C//qvAv/6rwK/+q8Cv/qvAr/6bsJ/+m7Cf/pugj/6rwK/+m7Cf/puwn/6bsJ/+m7
+ Cv/puwn/6bsJ/+m7Cf/puwn/6rwK/+m8Cf/qvAr/6r0J/+q9CP/rvQr/674K/+q/Cf/rwAv/7MEM/+zD
+ DP/sxQz/7MYO/+3HEf/tyRP/7csY//HNGf/t0jP/6tZh/+nYcP/p2XD/6tt2/+zdef/s3X3/7N9+/+zf
+ gf/r4YP/6+KI/+rhif/q4Yr/6uOL/+vki//q5JL/6uSV/+vjkv/p4ZH/7eqU//T2mv/f04v/m3Bf/2Ma
+ Ov9TACv/UwEn/2wtRv+mkp3/0ePw/9Do///C1Pv/vtL4/8HW+f/B1vv/x+L+/8zq//+sp8z/ZSNQ/31B
+ Pv/w7I//6uGK/+rhiP/p34X/6d6F/+vccv/t2U//7tY7/+7TNv/u0Sz/7tAi/+7OG//uyxX/7skR/+3I
+ Dv/sxg//68MM/+vBC//qwAv/678K/+y+Cf/rvgn/6r0J/+q9Cf/rvQr/6rwK/+q8Cv/qvAr/6bsK/+m6
+ Cf/ougj/6rwK/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bwJ/+q9
+ CP/qvAr/670K/+u+Cf/qvwr/68AK/+zCC//qwwn/7MQM/+7FDv/vxxD/7skT/+3LFv/vzRf/8M8o/+vV
+ Uv/q12//6tly/+rcc//s3Hf/7N16/+vdf//s3oD/6uCC/+nhg//r4Yb/6+KJ/+ziiP/q5Zb/6eOY/+vj
+ if/s5JD/6+OR/+jgj//u6ZT/9vWZ/9nNhv+Ya1r/YRc4/1AAKv9TAij/bjJL/6mYo//T5PH/zun//8DU
+ +//A1v3/u8rp/5Nief/L6P//h3Gf/3g4Mv/y7Yz/6d+E/+jghf/q34D/7dpm/+/XRP/v1Tn/7dMy/+3R
+ Kv/v0CH/784b/+/MFP/uyBL/7McQ/+zGDv/rww3/68EM/+vAC//qvwr/6r4J/+u+Cf/qvQj/6r0I/+q9
+ CP/qvQf/6r0J/+q8Cv/puwn/6bsK/+i5Cf/ougj/6bwI/+m8B//puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+q8Cf/qvQj/6r0I/+q9CP/rvgn/678K/+vAC//swQv/7cIN/+3E
+ C//txgz/7cgO/+7JEf/tyxb/78wZ//DOHv/u0z3/69dl/+rYcv/r2nP/69t1/+vbeP/r3Xr/6t59/+vf
+ gP/s4IL/6+GF/+rhiP/r4ob/6uOH/+rjlP/r4oz/7OOM/+zjjf/q4pD/6eGN/+/qkP/185X/1saB/5Ji
+ VP9gEzf/UQAq/1MDKP9wNk//rJ6p/9Lo9f/M5///vdP7/3hFZf+1u9b/h3Sj/3g4L//z6on/6t6C/+rd
+ cv/w2FL/8Nc8/+3UN//u0i//79En/+/PIP/uzRr/7MsW/+7JEv/uxxD/7MUO/+vDDf/swQz/6sAL/+q/
+ Cv/svwr/674J/+q9CP/qvQr/6r0J/+q9CP/qvAn/6rwK/+q7Cv/qvAr/6rwK/+m7Cf/ougf/6bwI/+m7
+ CP/puwf/6bsI/+m7Cf/puwn/6bsJ/+m7Cf/qvAr/6bsJ/+q8Cv/puwn/6bsJ/+m7Cf/pvAf/6r0I/+q9
+ CP/qvQj/674J/+u/Cv/qvwr/7MEM/+3DC//rxAv/7cYM/+7IDf/tyRD/7ssU/+7MGf/wzRr/79En/+vV
+ TP/q2Gn/69lz/+nac//q23T/7Nx4/+3de//s3n3/696A/+rfgf/r4IL/6uKE/+njlv/r4Yr/6uKJ/+ri
+ if/q4ov/6uOL/+rhi//o34r/8OqM//TykP/TwHv/jVtQ/1wQNf9TACv/VgQq/3U8VP+vpK//0un3/83o
+ /f/O7///hWqW/3k3Mv/z53v/7Ndb//DXQv/v1jj/7tQy/+3SLP/uzyT/7s4e/+/MGf/tyxb/7MkS/+3H
+ D//txQ7/7MMN/+vCDP/swAv/7L8L/+q+Cf/rvgn/674K/+u9Cv/rvQv/6r0K/+q9Cf/qvAr/6bwJ/+m8
+ CP/puwn/6bsJ/+m7Cf/ouwb/6rwJ/+m6Cf/pvAf/6bwI/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/pvAj/6r0I/+q9CP/qvQj/674J/+u+Cf/qvwr/68EL/+zCC//rwgz/7MQM/+3F
+ DP/uxw//7sgQ/+7JFP/tyxn/7s0Z//DPHP/u0y7/7NZO/+nYaf/q2XL/69p1/+rbdf/r3Hb/69x6/+vd
+ ff/q3oD/69+A/+regP/r4IL/6+GD/+rghf/r4IX/6uGG/+nhh//r4Ij/6d6I/+jdhv/v6Yn/8u6K/864
+ dP+IUUv/Wwwz/1IAKf9YBCr/eT1V/7Cirv+3utL/Xx1T/5VgMv/45Ej/7dE4/+/TNf/v0y3/7tEm/+/P
+ IP/vzRr/7swV/+3KE//tyg//7cgO/+zGDv/rwwz/68EL/+zAC//twAv/7L8K/+u+Cf/rvgn/674L/+q8
+ C//qvAr/6rwK/+q8Cv/quwr/6bwI/+q9B//puwn/6bsK/+m7Cf/qugj/67wJ/+m8B//pugn/6boK/+m6
+ Cv/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/pvAf/6r0I/+q9CP/qvQj/6r0I/+u+
+ Cf/svwr/7L8K/+zACv/rwAv/7cMN/+3DDP/sxQv/7ccO/+3IEP/tyRP/7ssV/+3MGv/vzhv/8M8f/+7S
+ Lv/s1Eb/69de/+rZbv/p2nb/6tt2/+vbd//r23r/6916/+vee//q3nz/695+/+vef//q3n//69+A/+re
+ gf/p34L/6d6B/+rfgf/p3YH/6Nx///Hohf/06Yb/ya5s/4NJSP9aDDX/TwAp/00BJf9QASf/fDso/+PH
+ Nv/w1jP/7NIu/+3RKP/vzyL/7s8d/+/MGP/tyxT/7soS/+7IEP/txg7/7MQN/+vEDf/swQz/7MAL/+3A
+ C//svwr/674J/+u+Cf/rvQv/670L/+u9Cv/qvAr/6rwK/+q8Cv/puwn/6bwI/+m8B//puwn/6bsJ/+i6
+ CP/qugj/67wJ/+m8CP/pugr/6boK/+i6Cf/pugr/6boJ/+m7Cf/puwn/6bsJ/+q8Cv/puwn/6bsJ/+m7
+ Cf/pvAf/6bwH/+m8B//qvQj/6r0I/+u+Cf/rvgn/674J/+y/Cv/qvwr/68AK/+zCC//sxAv/7cUM/+3G
+ Df/tyA7/7sgQ/+/JFP/tyxf/7swb/+7NHP/vzx7/8NEm/+3TN//r1E3/69df/+rZbP/q2nT/6dp3/+rb
+ ef/q23r/6tx6/+rcfP/p3Xz/6d19/+rdff/o3n7/6tx+/+rdfv/q3n//6t2A/+nafv/q23b/9OZv//Xk
+ Xv/Lpz7/qHQ1/616Mv/KoDD/7tMw//DVLP/t0Cj/7tAi/+7PHv/uzRr/7MwX/+3LFP/tyRL/7McQ/+3F
+ Dv/sxA3/68MN/+zCDP/rwAv/7L8K/+y/Cv/rvgr/674K/+u9Cv/rvQr/6rwK/+u9C//qvAr/6rwK/+m7
+ Cf/puwn/6bwI/+m8B//puwn/6bsJ/+m6Cf/puQf/6rsJ/+i6Cf/pugn/6bsK/+m7Cf/pugr/6bsK/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/pvAf/6r0I/+q9CP/qvQj/6r0I/+q9CP/qvQj/674J/+u+
+ Cf/pvgn/6r8J/+vBCv/swgz/7cQL/+3ECv/txgz/7scO/+3ID//syRD/7soT/+/LFv/uzBr/780d/+7O
+ Hf/vzyD/79Am/+7RMP/u0zv/7dVI/+rWU//p11z/6thi/+rZZv/q2Wr/69pr/+vaa//q2mj/69pm/+vZ
+ Yf/s2Vv/7ddS/+7XRv/w1D//7dE2/+3SMP/12zP/+OAx//neLf/22yj/7s8l/+3PIP/uzh7/8M0Z/+/N
+ F//vzBP/7ckR/+3HEf/txg//7MUN/+zDDf/rww3/68EM/+vAC//qvwr/678K/+u+Cf/rvQv/670L/+u9
+ C//rvQv/670L/+q8Cv/qvAr/6bsJ/+m7Cf/puwn/6bwI/+m8B//pvAn/6boK/+m5Cv/puQf/6rsJ/+m7
+ Cf/puwn/6bsK/+m6Cv/pugr/6bsK/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/qvAr/670K/+q8Cv/qvQj/6r0I/+u+Cf/rvgn/674J/+y/Cv/twAv/7MEL/+zDC//twwz/7cUM/+3G
+ DP/uxw3/7sgN/+/JEf/uyRP/7soW/+7MGP/uzBv/780d/+/NH//wzh//8M8g/+/QI//u0Cb/79Ep//DS
+ LP/w0y3/79Mt/+7TLf/w0jD/8NIu/+/SK//w0Sz/8NEr/+/SK//u0Sv/7tEr/+3QKv/tzSj/7Msm/+3M
+ If/tzR3/7s4b/+7NGf/tzBb/7coT/+7IEv/vyRD/7ccQ/+zFD//rxAz/7MMN/+3CDf/swQz/68AL/+q/
+ Cv/svwr/670K/+u9C//rvQv/670K/+q9CP/qvAr/6rwK/+q8Cv/qvAr/6rwJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsK/+m6Cv/qugj/6rsJ/+m7Cf/puwn/6boJ/+m6Cf/pugn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/qvAr/6rwK/+q8Cv/qvQj/6r0I/+q9CP/rvgn/674J/+y+
+ Cv/svwr/7MAK/+zBC//swgv/7MML/+zEC//txAz/7cYM/+3HDv/tyBD/7ckQ/+3JEv/uyhP/7soW/+7K
+ Gf/uzBv/7c0c/+3NHP/uzh3/784g/+/OIP/vzyH/788i/+7PI//uziP/7s8k/+/PJP/vziX/7s4j/+7P
+ I//uzyH/7s4d/+/PHP/uzR3/7s0b/+3NGP/vzBb/78sV/+7KE//vyhH/7sgQ/+zHEP/sxQ7/68QO/+vD
+ Df/rwgz/68EM/+zBDP/rwAv/68AL/+u/Cv/rvgn/674K/+u9Cv/qvQn/6r0J/+q9Cf/qvAr/6rwK/+m7
+ Cf/qvAn/6rsK/+m7Cf/puwn/6bsJ/+m7Cf/ouwj/6boJ/+i5Cf/puQf/6rsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+i6CP/puwn/6bsJ/+i6CP/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m8
+ Cf/qvQn/6r0I/+q9CP/qvQj/6r0I/+q9CP/rvgn/7L8K/+y/Cv/rwQr/7MEK/+zCC//twgz/7MML/+3G
+ DP/txg7/7ccP/+7HD//tyBD/7MkR/+zKEf/uyxL/7swT/+3ME//uyxX/7ssY/+/MGP/uzBn/7swZ/+7M
+ Gv/vzRr/780a/+7NGv/uzRr/7s0Z/+7NGP/szRf/7cwW/+7MFf/tyxT/7ssT/+7KEv/uyRP/7cgR/+zH
+ D//txw//7MYO/+zFDf/sww3/68MN/+vCDP/swQz/68AL/+vAC//qvwr/678K/+y+Cf/rvgn/674J/+q9
+ CP/qvQj/6r0J/+q8Cv/qvAr/6rwK/+m7Cf/puwj/6bsK/+m8Cf/puwn/6bsJ/+m7Cf/puwn/6boJ/+i5
+ Cf/qugj/6rwI/+m8B//puwn/6bsJ/+m7Cf/pugr/6boJ/+m7Cf/puwn/6bsJ/+q8Cv/puwn/6bwJ/+m7
+ Cf/pugr/6rsK/+m7CP/puwn/6rwK/+m7Cf/pvAn/6rwK/+q8Cv/qvAr/6r0J/+q9CP/rvgj/674J/+y/
+ Cv/twAv/68AL/+vAC//swQz/7MEM/+3CDf/tww3/7cUM/+zGDP/rxw3/7MgN/+zIDf/vyQ7/7skQ/+3J
+ Ef/tyRH/7coS/+7KE//uyhL/7ssS/+7LEv/uyxL/7ssT/+3KEv/uyxL/7soS/+3KEf/tyhH/7ssS/+7K
+ Ef/vyRH/78kQ/+7ID//tyBD/7cYP/+zFDv/sxA3/7MMN/+3CDf/swg3/7MEM/+zBDP/qwAr/6r8K/+q/
+ Cv/rvwn/674J/+u+Cf/rvQv/6rwK/+q8Cv/qvAr/6rwK/+q8Cv/pvAf/6bwI/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6bsJ/+m7Cf/puwn/6bsJ/+i6CP/pugj/6bsI/+m7CP/puwn/6bsJ/+i6Cf/pugr/6boJ/+m7
+ Cf/ougn/6boJ/+q7Cv/puwr/6bsK/+m7Cv/puwr/6bsJ/+m7Cf/pvAn/6bsK/+m7Cv/puwn/6bsK/+m7
+ Cf/qvAr/6rwK/+q9Cf/rvQr/674J/+u+Cf/svgr/678K/+u/Cv/rwAv/68AL/+vAC//swgv/7MML/+zE
+ C//txAz/7cQM/+3FDf/txg3/7MYO/+3HD//txxD/7ccP/+7ID//uyQ3/7skN/+7IDf/vyQ//7sgP/+7I
+ D//tyBD/7ccQ/+3HD//txxD/7ccQ/+3HD//txw//7MUO/+zFDv/rxQz/7MMM/+zCDf/rwgv/68EL/+zB
+ DP/swQv/68AL/+vAC//rvwv/678L/+u/Cv/rvgr/674K/+u+Cv/rvQv/6rwK/+q8Cv/qvAr/6rwK/+q8
+ Cv/pvAj/6bwJ/+m7Cv/puwn/6bsJ/+m7Cf/qvAr/6bwJ/+m7Cf/puwn/6boJ/+i5CP/puwn/6rwK/+m7
+ Cf/ougj/6LoI/+m7Cf/pugr/6boJ/+i6Cf/pugr/6boK/+m6Cv/puQr/6boK/+m6Cv/puwn/6bsJ/+m7
+ Cf/puwn/6rsK/+m6Cv/puwn/6bsJ/+q7Cv/qvAr/6rwK/+q8Cv/qvAv/670K/+q9CP/qvQn/674J/+u+
+ Cf/qvwr/6r8K/+vAC//rwQr/68EK/+zCC//twgz/7MMM/+vDDP/rwwz/7MMN/+zEDf/txA7/7cUN/+3G
+ DP/txg3/7cYN/+3GDf/txg//7MYN/+zGDP/sxg7/7MUO/+3EDv/txA7/7MQN/+3EDf/sxA3/68MN/+vD
+ Df/rwg3/68EM/+zADP/rwA3/68AM/+vAC//rvwv/678L/+q/Cv/rvQz/674K/+u+Cf/rvQv/67wL/+u9
+ C//qvAr/6rwK/+q8Cv/puwr/6rwK/+q8Cv/puwr/6bsK/+q8Cv/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7
+ Cf/puwn/6boJ/+i5Cf/puwj/6rwJ/+m8Cf/ougj/6boJ/+m6Cf/ouQn/6LoJ/+i6CP/ougn/6boK/+i6
+ Cf/ouQn/6boJ/+m6Cv/pugr/6boK/+m6Cv/puwn/6bsJ/+m7Cf/puwn/6bsJ/+m7Cv/qvAr/6rwK/+m8
+ Cf/qvAr/6rwK/+q8Cv/qvQj/674J/+u+CP/rvgn/6r4J/+m+Cf/qvwr/6r8K/+vACv/rwAr/68AL/+vB
+ C//swQv/7MEL/+zBDP/twwv/7MMN/+vDDf/rww7/68MM/+vDDP/rww3/68MN/+vDDf/rxA3/68MN/+vD
+ DP/swgz/7MEM/+zBDP/qwgz/6sEL/+vAC//rwAz/68AL/+vAC//qwA3/678M/+y+DP/svgz/674L/+u9
+ C//pvgv/6r4L/+u9Cv/qvAr/6rwK/+q8Cv/qvAr/6bsJ/+q7Cv/quwr/6rsL/+q7C//quwr/6boK/+m6
+ Cv/puwn/6bsJ/+m6Cf/pugr/6boJ/+m6Cf/pugn/6boJ/+i5Cf/ouQr/6LoK/+i5Cf/ouQn/6boJ/+i5
+ Cf/ouAr/6LkK/+i5Cf/ouQr/6LgK/+i4Cv/ouAr/6LkK/+i5Cf/ouQn/6LkJ/+i5Cv/ouQn/6LoJ/+i6
+ Cf/ougn/6LoJ/+m6Cv/pugr/6boL/+m7C//quwr/6bsK/+m7Cf/qvAn/6rwJ/+q8Cf/qvAr/6bwK/+m9
+ Cv/qvQr/6b0J/+m+Cf/qvgv/6r4L/+q+C//qvgz/6r8M/+q/DP/rwAz/68AM/+rBDP/rwQz/6sEL/+rB
+ C//qwQz/6sEL/+vBDP/qwQz/6sAL/+rAC//rwAz/68AM/+q/C//pwAz/6r8L/+q/Cv/rvwr/6r4K/+q+
+ C//qvgv/6r0L/+u8C//rvAv/67wL/+u8C//pvAv/6rwL/+q7C//puwr/6boK/+m6Cv/pugr/6boK/+m6
+ C//puQv/6bkK/+m6C//puQv/6LkK/+m5Cv/ouQr/6LkK/+i5Cv/puQr/6LkK/+i5Cv/ouQr/6LkL/+i4
+ Cv/ouQv/6boL/+e5CP/nugf/6LkJ/+i5Cv/ouAr/6LkJ/+i5Cf/ouQn/6LkI/+i5Cf/ouQj/6LkJ/+i5
+ Cv/ouQn/6LkJ/+i5Cf/ougf/6LoI/+i6CP/ougj/6LsI/+i7CP/pugr/6bsL/+m6Cv/puwn/6bsK/+m7
+ Cv/puwr/6bsK/+m7Cv/puwn/6rwK/+q8Cv/quwv/6bwK/+i9Cv/ovQr/6L0K/+m+Cv/pvgv/6b4L/+q+
+ C//pvgr/6r4L/+q/C//qvwz/6r8M/+q/DP/qwAr/6sAJ/+q/Cv/qvwz/6r8L/+q/C//qvwv/6b8J/+rA
+ Cf/qvwr/6b4K/+m+C//qvQr/6rwJ/+q8Cf/rvAv/6rwL/+q8Cv/qvAr/6rwK/+q8Cv/qvAn/6bsJ/+m7
+ Cf/puwn/6boJ/+i6Cf/pugr/6LoJ/+i6Cf/ougn/6LoJ/+i6Cf/pugn/6LoK/+i5Cf/ouQn/6LoJ/+i6
+ Cf/nuQj/6LoJ/+i5Cf/nuQj/6LkK/+i5Cv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
+
+
+
\ No newline at end of file
diff --git a/ExplorerTabUtility/Forms/TrayIcon.cs b/ExplorerTabUtility/Forms/TrayIcon.cs
deleted file mode 100644
index 03fe09f..0000000
--- a/ExplorerTabUtility/Forms/TrayIcon.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading;
-using System.Diagnostics;
-using System.Windows.Forms;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using FlaUI.Core.AutomationElements;
-using Microsoft.Win32;
-using ExplorerTabUtility.Hooks;
-using ExplorerTabUtility.Models;
-using ExplorerTabUtility.WinAPI;
-using ExplorerTabUtility.Helpers;
-using Window = ExplorerTabUtility.Models.Window;
-
-namespace ExplorerTabUtility.Forms;
-
-public class TrayIcon : ApplicationContext
-{
- private static IntPtr _mainWindowHandle = IntPtr.Zero;
- private static readonly NotifyIcon NotifyIcon;
- private static readonly Keyboard KeyboardHook;
- private static readonly Shell32 WindowHook;
- private static readonly SemaphoreSlim Limiter;
- private static WindowHookVia _windowHookVia;
-
- static TrayIcon()
- {
- Limiter = new SemaphoreSlim(1);
- WindowHook = new Shell32(OnNewWindow);
- KeyboardHook = new Keyboard(OnNewWindow);
-
- var isKeyboardHookActive = Properties.Settings.Default.KeyboardHook;
- var isWindowHookActive = Properties.Settings.Default.WindowHook;
- _windowHookVia = Properties.Settings.Default.WindowViaUi
- ? WindowHookVia.Ui
- : WindowHookVia.Keys;
-
- NotifyIcon = new NotifyIcon
- {
- Icon = Helper.GetIcon(),
- Text = "Explorer Tab Utility: Force new windows to tabs.",
-
- ContextMenuStrip = CreateContextMenuStrip(isKeyboardHookActive, isWindowHookActive),
- Visible = true
- };
-
- if (isKeyboardHookActive) KeyboardHook.StartHook();
- if (isWindowHookActive) WindowHook.StartHook();
-
- Application.ApplicationExit += OnApplicationExit;
- }
- private static ContextMenuStrip CreateContextMenuStrip(bool isKeyboardHookActive, bool isWindowHookActive)
- {
- var strip = new ContextMenuStrip();
-
- strip.Items.Add(CreateToolStripMenuItem("Keyboard (Win + E)", isKeyboardHookActive, ToggleKeyboardHook));
- strip.Items.Add(CreateWindowHookMenuItem(isWindowHookActive));
-
- strip.Items.Add(new ToolStripSeparator());
- strip.Items.Add(CreateToolStripMenuItem("Add to startup", IsInStartup(), ToggleStartup));
-
- strip.Items.Add(new ToolStripSeparator());
- strip.Items.Add(CreateToolStripMenuItem("Exit", false, static (_, _) => Application.Exit()));
-
- return strip;
- }
- private static ToolStripMenuItem CreateWindowHookMenuItem(bool isWindowHookActive)
- {
- var windowHookMenuItem = CreateToolStripMenuItem("All Windows", isWindowHookActive, ToggleWindowHook);
-
- windowHookMenuItem.DropDownItems.Add(
- CreateToolStripMenuItem("UI (Recommended)", _windowHookVia == WindowHookVia.Ui, WindowHookViaChanged, "WindowViaUi"));
-
- windowHookMenuItem.DropDownItems.Add(
- CreateToolStripMenuItem("Keys", _windowHookVia == WindowHookVia.Keys, WindowHookViaChanged, "WindowViaKeys"));
-
- return windowHookMenuItem;
- }
- private static ToolStripMenuItem CreateToolStripMenuItem(string text, bool isChecked, EventHandler eventHandler, string? name = default)
- {
- var item = new ToolStripMenuItem
- {
- Text = text,
- Checked = isChecked
- };
-
- if (name != default)
- item.Name = name;
-
- item.Click += eventHandler;
- return item;
- }
-
- private static bool IsInStartup()
- {
- var executablePath = Process.GetCurrentProcess().MainModule?.FileName;
- if (string.IsNullOrWhiteSpace(executablePath)) return false;
-
- using var key = Registry.CurrentUser.OpenSubKey(Constants.RunRegistryKeyPath, false);
- if (key == default) return false;
-
- var value = key.GetValue(Constants.AppName) as string;
- return string.Equals(value, executablePath, StringComparison.OrdinalIgnoreCase);
- }
- private static void ToggleStartup(object? sender, EventArgs _)
- {
- if (sender is not ToolStripMenuItem item) return;
-
- var executablePath = Process.GetCurrentProcess().MainModule?.FileName;
- if (string.IsNullOrWhiteSpace(executablePath)) return;
-
- using var key = Registry.CurrentUser.OpenSubKey(Constants.RunRegistryKeyPath, true);
- if (key == default) return;
-
- // If already set in startup
- if (string.Equals(key.GetValue(Constants.AppName) as string, executablePath, StringComparison.OrdinalIgnoreCase))
- {
- // Remove from startup
- key.DeleteValue(Constants.AppName, false);
- item.Checked = false;
- }
- else
- {
- // Add to startup
- key.SetValue(Constants.AppName, executablePath);
- item.Checked = true;
- }
- }
- private static void ToggleKeyboardHook(object? sender, EventArgs _)
- {
- if (sender is not ToolStripMenuItem item) return;
-
- item.Checked = !item.Checked;
-
- Properties.Settings.Default.KeyboardHook = item.Checked;
- Properties.Settings.Default.Save();
-
- if (item.Checked)
- KeyboardHook.StartHook();
- else
- KeyboardHook.StopHook();
- }
- private static void ToggleWindowHook(object? sender, EventArgs _)
- {
- if (sender is not ToolStripMenuItem item) return;
-
- item.Checked = !item.Checked;
-
- Properties.Settings.Default.WindowHook = item.Checked;
- Properties.Settings.Default.Save();
-
- if (item.Checked)
- WindowHook.StartHook();
- else
- WindowHook.StopHook();
-
- foreach (ToolStripItem subItem in item.DropDownItems)
- subItem.Enabled = item.Checked;
- }
- private static void WindowHookViaChanged(object? sender, EventArgs _)
- {
- if (sender is not ToolStripMenuItem item) return;
-
- var container = item.GetCurrentParent();
- foreach (ToolStripMenuItem radio in container.Items)
- {
- radio.Checked = !radio.Checked;
-
- if (radio.Name == "WindowViaUi")
- Properties.Settings.Default.WindowViaUi = radio.Checked;
- else if (radio.Name == "WindowViaKeys")
- Properties.Settings.Default.WindowViaKeys = radio.Checked;
- }
-
- Properties.Settings.Default.Save();
-
- _windowHookVia = Properties.Settings.Default.WindowViaUi
- ? WindowHookVia.Ui
- : WindowHookVia.Keys;
- }
-
- private static async Task OnNewWindow(Window window)
- {
- var windowHandle = IntPtr.Zero;
- try
- {
- await Limiter.WaitAsync().ConfigureAwait(false);
-
- windowHandle = GetMainWindowHWnd(window.OldWindowHandle);
- if (windowHandle == default) return;
-
- var windowElement = UiAutomation.FromHandle(windowHandle);
- if (windowElement == default) return;
-
- // Store currently opened Tabs, before we open a new one.
- var oldTabs = WinApi.GetAllExplorerTabs();
-
- // Add new tab.
- AddNewTab(windowElement);
-
- // If it is just a new (This PC | Home), return.
- if (string.IsNullOrWhiteSpace(window.Path)) return;
-
- // Get newly created tab's handle (That is not in 'oldTabs')
- var newTabHandle = WinApi.ListenForNewExplorerTab(oldTabs);
- if (newTabHandle == 0) return;
-
- // Get the tab element out of that handle.
- var newTabElement = UiAutomation.FromHandle(newTabHandle);
- if (newTabElement == default) return;
-
- // Navigate to the target location
- Navigate(windowElement, newTabHandle, window.Path);
-
- if (window.SelectedItems is not { Count: > 0 } selectedItems) return;
-
- // Select items
- SelectItems(newTabElement, selectedItems);
- }
- finally
- {
- if (windowHandle != default)
- WinApi.RestoreWindowToForeground(windowHandle);
-
- Limiter.Release();
- }
- }
-
- private static IntPtr GetMainWindowHWnd(IntPtr otherThan)
- {
- if (WinApi.IsWindowHasClassName(_mainWindowHandle, "CabinetWClass"))
- return _mainWindowHandle;
-
- var allWindows = WinApi.FindAllWindowsEx();
-
- // Get another handle other than the newly created one. (In case if it still alive.)
- _mainWindowHandle = allWindows.FirstOrDefault(t => t != otherThan);
-
- return _mainWindowHandle;
- }
- private static void AddNewTab(AutomationElement window)
- {
- // if via UI is selected try to add a new tab with UI Automation.
- if (_windowHookVia == WindowHookVia.Ui && UiAutomation.AddNewTab(window))
- return;
-
- // Via Keys is selected or UI Automation fails.
- Keyboard.AddNewTab(window.Properties.NativeWindowHandle.Value);
- }
- private static void Navigate(AutomationElement window, nint tabHandle, string location)
- {
- // if via UI is selected try to Navigate with UI Automation.
- if (_windowHookVia == WindowHookVia.Ui && UiAutomation.Navigate(window, tabHandle, location))
- return;
-
- // Via Keys is selected or UI Automation fails.
- Keyboard.Navigate(window.Properties.NativeWindowHandle.Value, tabHandle, location);
- }
- private static void SelectItems(AutomationElement tab, ICollection names)
- {
- // if via UI is selected try to Select with UI Automation.
- if (_windowHookVia == WindowHookVia.Ui && UiAutomation.SelectItems(tab, names))
- return;
-
- // Via Keys is selected or UI Automation fails.
- Keyboard.SelectItems(tab.Properties.NativeWindowHandle.Value, names);
- }
- private static void OnApplicationExit(object? _, EventArgs __)
- {
- NotifyIcon.Visible = false;
- KeyboardHook.Dispose();
- WindowHook.Dispose();
- }
-}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Helpers/Constants.cs b/ExplorerTabUtility/Helpers/Constants.cs
index 4fca603..aa53b9d 100644
--- a/ExplorerTabUtility/Helpers/Constants.cs
+++ b/ExplorerTabUtility/Helpers/Constants.cs
@@ -5,4 +5,7 @@ internal class Constants
internal const string AppName = "ExplorerTabUtility";
internal const string MutexId = $"__{AppName}Hook__Mutex";
internal const string RunRegistryKeyPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
+ internal const string NotifyIconText = @"Explorer Tab Utility: Force new windows to tabs.";
+ internal const string HotKeyProfilesFileName = "HotKeyProfiles.json";
+ internal const string JsonFileFilter = @"JSON files (*.json)|*.json|All Files|*.*";
}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Helpers/Helper.cs b/ExplorerTabUtility/Helpers/Helper.cs
index 911aebf..8b8fd32 100644
--- a/ExplorerTabUtility/Helpers/Helper.cs
+++ b/ExplorerTabUtility/Helpers/Helper.cs
@@ -3,11 +3,37 @@
using System.Diagnostics;
using System.Drawing;
using System.Threading;
+using System.Threading.Tasks;
namespace ExplorerTabUtility.Helpers;
public static class Helper
{
+ public static Task DoDelayedBackgroundAsync(Action action, int delayMs = 2_000, CancellationToken cancellationToken = default)
+ {
+ return Task.Run(async () =>
+ {
+ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
+ action();
+ }, cancellationToken);
+ }
+ public static Task DoDelayedBackgroundAsync(Func action, int delayMs = 2_000, CancellationToken cancellationToken = default)
+ {
+ return Task.Run(async () =>
+ {
+ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
+ await action().ConfigureAwait(false);
+ }, cancellationToken);
+ }
+ public static Task DoDelayedBackgroundAsync(Func> action, int delayMs = 2_000, CancellationToken cancellationToken = default)
+ {
+ return Task.Run(async () =>
+ {
+ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
+ return await action().ConfigureAwait(false);
+ }, cancellationToken);
+ }
+
public static T DoUntilNotDefault(Func action, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
{
return DoUntilCondition(
@@ -64,6 +90,86 @@ public static void DoIfCondition(Action action, Func predicate, bool justO
Thread.Sleep(sleepMs);
}
}
+ public static Task DoUntilNotDefaultAsync(Func> action, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ return DoUntilConditionAsync(
+ action,
+ result => !EqualityComparer.Default.Equals(result, default),
+ timeMs,
+ sleepMs,
+ cancellationToken);
+ }
+ public static Task DoUntilNotDefaultAsync(Func action, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ return DoUntilConditionAsync(
+ action,
+ result => !EqualityComparer.Default.Equals(result, default),
+ timeMs,
+ sleepMs,
+ cancellationToken);
+ }
+ public static Task DoUntilTimeEndAsync(Func action, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ return DoUntilConditionAsync(action, static () => false, timeMs, sleepMs, cancellationToken);
+ }
+ public static async Task DoUntilConditionAsync(Func action, Func predicate, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ var startTicks = Stopwatch.GetTimestamp();
+
+ while (!cancellationToken.IsCancellationRequested && !IsTimeUp(startTicks, timeMs))
+ {
+ await action().ConfigureAwait(false);
+ if (predicate())
+ return;
+
+ await Task.Delay(sleepMs).ConfigureAwait(false);
+ }
+ }
+ public static async Task DoUntilConditionAsync(Func action, Predicate predicate, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ var startTicks = Stopwatch.GetTimestamp();
+
+ while (!cancellationToken.IsCancellationRequested && !IsTimeUp(startTicks, timeMs))
+ {
+ var result = action();
+ if (predicate(result))
+ return result;
+
+ await Task.Delay(sleepMs).ConfigureAwait(false);
+ }
+
+ return action();
+ }
+ public static async Task DoUntilConditionAsync(Func> action, Predicate predicate, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ var startTicks = Stopwatch.GetTimestamp();
+
+ while (!cancellationToken.IsCancellationRequested && !IsTimeUp(startTicks, timeMs))
+ {
+ var result = await action().ConfigureAwait(false);
+ if (predicate(result))
+ return result;
+
+ await Task.Delay(sleepMs).ConfigureAwait(false);
+ }
+
+ return await action().ConfigureAwait(false);
+ }
+ public static async Task DoIfConditionAsync(Func action, Func predicate, bool justOnce = false, int timeMs = 500, int sleepMs = 20, CancellationToken cancellationToken = default)
+ {
+ var startTicks = Stopwatch.GetTimestamp();
+
+ while (!cancellationToken.IsCancellationRequested && !IsTimeUp(startTicks, timeMs))
+ {
+ if (predicate())
+ {
+ await action().ConfigureAwait(false);
+
+ if (justOnce) return;
+ }
+ await Task.Delay(sleepMs).ConfigureAwait(false);
+ }
+ }
public static bool IsTimeUp(long startTicks, int timeMs)
{
@@ -81,15 +187,18 @@ public static TimeSpan GetElapsedTime(long startTicks)
var tickFrequency = (double)10_000_000 / Stopwatch.Frequency;
return new TimeSpan((long)((Stopwatch.GetTimestamp() - startTicks) * tickFrequency));
}
+ public static T Clamp(this T val, T min, T max) where T : IComparable
+ {
+ if (val.CompareTo(min) < 0) return min;
+ if (val.CompareTo(max) > 0) return max;
+ return val;
+ }
+
+ public static Icon? GetIcon() => Icon.ExtractAssociatedIcon(GetExecutablePath());
- public static Icon? GetIcon()
+ public static string GetExecutablePath()
{
var processName = Process.GetCurrentProcess().MainModule?.FileName;
-
- var location = string.IsNullOrWhiteSpace(processName)
- ? $"{AppDomain.CurrentDomain.FriendlyName}.exe"
- : processName;
-
- return Icon.ExtractAssociatedIcon(location);
+ return processName is { Length: > 0 } ? processName : $"{AppDomain.CurrentDomain.FriendlyName}.exe";
}
}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Hooks/IHook.cs b/ExplorerTabUtility/Hooks/IHook.cs
new file mode 100644
index 0000000..7fed91e
--- /dev/null
+++ b/ExplorerTabUtility/Hooks/IHook.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ExplorerTabUtility.Hooks;
+
+public interface IHook : IDisposable
+{
+ public bool IsHookActive { get; }
+ public void StartHook();
+ public void StopHook();
+}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Hooks/Keyboard.cs b/ExplorerTabUtility/Hooks/Keyboard.cs
index e1f1719..67090ab 100644
--- a/ExplorerTabUtility/Hooks/Keyboard.cs
+++ b/ExplorerTabUtility/Hooks/Keyboard.cs
@@ -1,124 +1,131 @@
-using WindowsInput;
-using System;
+using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
-using System.Runtime.InteropServices;
+using ExplorerTabUtility.Managers;
+using WindowsInput;
+using H.Hooks;
using ExplorerTabUtility.Models;
using ExplorerTabUtility.WinAPI;
-using System.Threading;
+using ExplorerTabUtility.Helpers;
namespace ExplorerTabUtility.Hooks;
-public class Keyboard : IDisposable
+public class Keyboard : IHook
{
- private nint _hookId = 0;
- private nint _user32LibraryHandle = 0;
- private bool _isWinKeyDown;
- private HookProc? _keyboardHookCallback; // We have to keep a reference because of GC
- private readonly Func _onNewWindow;
+ private readonly LowLevelKeyboardHook _lowLevelKeyboardHook;
+ private readonly IReadOnlyCollection _hotkeyProfiles;
+ private readonly Action _onHotKeyProfileTriggered;
private static readonly IKeyboardSimulator KeyboardSimulator = new InputSimulator().Keyboard;
+ public bool IsHookActive => _lowLevelKeyboardHook.IsStarted;
- public Keyboard(Func onNewWindow)
+ public Keyboard(IReadOnlyCollection hotkeyProfiles, Action onHotKeyProfileTriggered)
{
- _onNewWindow = onNewWindow;
+ _hotkeyProfiles = hotkeyProfiles;
+ _onHotKeyProfileTriggered = onHotKeyProfileTriggered;
+ _lowLevelKeyboardHook = new LowLevelKeyboardHook { Handling = true };
+ _lowLevelKeyboardHook.Down += LowLevelKeyboardHook_Down;
}
- public void StartHook()
- {
- _keyboardHookCallback = KeyboardHookCallback;
- _user32LibraryHandle = WinApi.LoadLibrary("User32");
- _hookId = WinApi.SetWindowsHookEx(WinHookType.WH_KEYBOARD_LL, _keyboardHookCallback, _user32LibraryHandle, 0);
- }
+ public void StartHook() => _lowLevelKeyboardHook.Start();
+ public void StopHook() => _lowLevelKeyboardHook.Stop();
- public void StopHook()
+ private void LowLevelKeyboardHook_Down(object? sender, KeyboardEventArgs e)
{
- Dispose();
- }
+ var isFileExplorerForeground = WinApi.IsFileExplorerForeground(out _);
+
+ foreach (var profile in _hotkeyProfiles)
+ {
+ // Skip if the profile is disabled or if it doesn't have any hotkeys.
+ if (!profile.IsEnabled || profile.HotKeys?.Any() != true) continue;
+
+ // Skip if the profile is for File Explorer but File Explorer is not the foreground window.
+ if (profile.Scope == HotkeyScope.FileExplorer && !isFileExplorerForeground) continue;
+
+ // Skip if the hotkeys don't match.
+ if (!e.Keys.Are(profile.HotKeys)) continue;
- private nint KeyboardHookCallback(int nCode, nint wParam, nint lParam)
+ // Set handled value.
+ e.IsHandled = profile.IsHandled;
+
+ // Invoke the profile action in the background in order for `IsHandled` to successfully prevent further processing.
+ Task.Run(() => _onHotKeyProfileTriggered(profile));
+ }
+ }
+ public static async Task GetCurrentTabLocationAsync(nint windowHandle, bool restoreToForeground = true)
{
- if (nCode < 0)
- return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam);
+ // Restore the window to foreground.
+ if (restoreToForeground)
+ {
+ WinApi.RestoreWindowToForeground(windowHandle);
+ await Task.Delay(350).ConfigureAwait(false);
+ }
- // Read key
- var vkCode = Marshal.ReadInt32(lParam);
+ // Send CTRL + L to activate the address bar
+ KeyboardSimulator.ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_L);
+ await Task.Delay(150).ConfigureAwait(false);
- // Windows key
- if (vkCode == WinApi.VK_WIN)
- _isWinKeyDown = wParam == WinApi.WM_KEYDOWN; //DOWN or UP
+ // Store the current clipboard data
+ var backup = ClipboardManager.GetClipboardData();
- if (!_isWinKeyDown || vkCode != WinApi.VK_E || wParam != WinApi.WM_KEYDOWN)
- return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam);
+ // Clear the clipboard.
+ ClipboardManager.ClearClipboard();
- // No Explorer windows, Continue with normal flow.
- if (!WinApi.FindAllWindowsEx().Take(1).Any())
- return WinApi.CallNextHookEx(_hookId, nCode, wParam, lParam);
+ // Send CTRL + C to copy the address location.
+ KeyboardSimulator.ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_C);
- // It is better not to wait for the invocation, otherwise the normal flow might open a new window
- Task.Run(() => _onNewWindow.Invoke(new Window(string.Empty)));
+ // Get the text from the clipboard.
+ var addressLocation = await Helper.DoUntilConditionAsync(
+ action: ClipboardManager.GetClipboardText,
+ predicate: l => !string.IsNullOrWhiteSpace(l))
+ .ConfigureAwait(false);
+
+ // Give the focus to the window to close the address bar.
+ WinApi.PostMessage(windowHandle, WinApi.WM_SETFOCUS, 0, 0);
+
+ // Restore the previous clipboard data.
+ ClipboardManager.SetClipboardData(backup);
- // Return dummy value to prevent normal flow.
- return 1;
+ return addressLocation;
}
- public static void AddNewTab(nint windowHandle)
+ public static async Task AddNewTabAsync(nint windowHandle)
{
// Restore the window to foreground.
WinApi.RestoreWindowToForeground(windowHandle);
- // Give the focus to the folder view.
- WinApi.PostMessage(windowHandle, WinApi.WM_SETFOCUS, 0, 0);
+ await Task.Delay(200).ConfigureAwait(false);
// Send CTRL + T
- KeyboardSimulator
- .Sleep(300)
- .ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_T);
+ KeyboardSimulator.ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_T);
}
- public static void Navigate(nint windowHandle, nint tabHandle, string location)
+ public static async Task NavigateAsync(nint windowHandle, string location)
{
// Restore the window to foreground.
WinApi.RestoreWindowToForeground(windowHandle);
- // Give the keyboard focus to the tab.
- WinApi.PostMessage(tabHandle, WinApi.WM_SETFOCUS, 0, 0);
+ await Task.Delay(300).ConfigureAwait(false);
// Send CTRL + L to activate the address bar
- KeyboardSimulator
- .Sleep(300)
- .ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_L);
+ KeyboardSimulator.ModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.VK_L);
- // Type the location.
- KeyboardSimulator
- .Sleep(300)
- .TextEntry(location)
- .Sleep(250 + location.Length * 5) // Longer locations require longer wait time.
- .KeyPress(VirtualKeyCode.RETURN); // Press Enter
-
- // Do in the background
- Task.Run(async () =>
- {
- // for ~1250 Milliseconds (25 * 50)
- for (var i = 0; i < 25; i++)
- {
- await Task.Delay(50);
+ await Task.Delay(300).ConfigureAwait(false);
- var popupHandle = WinApi.GetWindow(windowHandle, WinApi.GW_ENABLEDPOPUP);
+ // Type the location.
+ KeyboardSimulator.TextEntry(location);
- // If the suggestion popup is not visible, continue.
- if (popupHandle == 0) continue;
+ // Longer locations require longer wait time.
+ await Task.Delay(270 + location.Length * 5).ConfigureAwait(false);
- // Hide the suggestion popup.
- WinApi.ShowWindow(popupHandle, WinApi.SW_HIDE);
- }
- });
+ // Press Enter
+ KeyboardSimulator.KeyPress(VirtualKeyCode.RETURN);
}
- public static void SelectItems(nint tabHandle, ICollection names)
+ public static async Task SelectItemsAsync(nint tabHandle, ICollection names)
{
// Restore the window to foreground.
WinApi.RestoreWindowToForeground(tabHandle);
- Thread.Sleep(500);
+ await Task.Delay(500).ConfigureAwait(false);
// Type the first name.
KeyboardSimulator.TextEntry(names.First());
@@ -126,21 +133,9 @@ public static void SelectItems(nint tabHandle, ICollection names)
public void Dispose()
{
- if (_hookId != IntPtr.Zero)
- {
- WinApi.UnhookWindowsHookEx(_hookId);
- _hookId = IntPtr.Zero;
- }
-
- _keyboardHookCallback = null;
- if (_user32LibraryHandle == IntPtr.Zero) return;
-
- // reduces reference to library by 1.
- WinApi.FreeLibrary(_user32LibraryHandle);
- _user32LibraryHandle = IntPtr.Zero;
- }
- ~Keyboard()
- {
- Dispose();
+ StopHook();
+ _lowLevelKeyboardHook.Dispose();
}
+
+ ~Keyboard() => Dispose();
}
\ No newline at end of file
diff --git a/ExplorerTabUtility/Hooks/Shell32.cs b/ExplorerTabUtility/Hooks/Shell32.cs
index 6231979..62d931f 100644
--- a/ExplorerTabUtility/Hooks/Shell32.cs
+++ b/ExplorerTabUtility/Hooks/Shell32.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -9,34 +10,31 @@
namespace ExplorerTabUtility.Hooks;
-public class Shell32 : IDisposable
+public class Shell32(Action onNewWindow) : IHook
{
- private IntPtr _hookId = IntPtr.Zero;
+ private nint _hookId;
private WinEventDelegate? _eventHookCallback; // We have to keep a reference because of GC
- private readonly Func _onNewWindow;
private static object? _shell;
private static Type? _shellType;
private static Type? _windowType;
-
- public Shell32(Func onNewWindow)
- {
- _onNewWindow = onNewWindow;
- }
+ public bool IsHookActive { get; private set; }
public void StartHook()
{
_eventHookCallback = OnWindowOpenHandler;
- _hookId = WinApi.SetWinEventHook(WinApi.EVENT_OBJECT_CREATE, WinApi.EVENT_OBJECT_CREATE, IntPtr.Zero, _eventHookCallback, 0, 0, 0);
+ _hookId = WinApi.SetWinEventHook(WinApi.EVENT_OBJECT_CREATE, WinApi.EVENT_OBJECT_CREATE, default, _eventHookCallback, 0, 0, 0);
+ IsHookActive = true;
}
public void StopHook()
{
- Dispose();
+ WinApi.UnhookWinEvent(_hookId);
+ IsHookActive = false;
}
- private void OnWindowOpenHandler(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dWmsEventTime)
+ private void OnWindowOpenHandler(nint hWinEventHook, uint eventType, nint hWnd, int idObject, int idChild, uint dwEventThread, uint dWmsEventTime)
{
if (!WinApi.IsWindowHasClassName(hWnd, "CabinetWClass")) return;
- if (WinApi.FindAllWindowsEx().Take(2).Count() < 2) return;
+ if (WinApi.FindAllWindowsEx("CabinetWClass").Take(2).Count() < 2) return;
var originalRect = WinApi.HideWindow(hWnd);
var showAgain = true;
@@ -69,7 +67,7 @@ private void OnWindowOpenHandler(IntPtr hWinEventHook, uint eventType, IntPtr hW
{
// Move the back to the screen (Show)
if (showAgain)
- WinApi.SetWindowPos(hWnd, IntPtr.Zero, originalRect.Left, originalRect.Top, 0, 0, WinApi.SWP_NOSIZE | WinApi.SWP_NOZORDER);
+ WinApi.SetWindowPos(hWnd, default, originalRect.Left, originalRect.Top, 0, 0, WinApi.SWP_NOSIZE | WinApi.SWP_NOZORDER);
}
}
@@ -81,7 +79,7 @@ private void OnWindowOpenHandler(IntPtr hWinEventHook, uint eventType, IntPtr hW
_shell ??= Activator.CreateInstance(_shellType);
if (_shell == default) return default;
- var windows = _shellType.InvokeMember("Windows", BindingFlags.InvokeMethod, null, _shell, Array.Empty