diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e69de29
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f825da6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# .gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
diff --git a/SshHandler.sln b/SshHandler.sln
new file mode 100644
index 0000000..deffb31
--- /dev/null
+++ b/SshHandler.sln
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{50924D75-2903-46D4-86DD-02269EFDBCA9}") = "SshHandler", "SshHandler\SshHandler.csproj", "{960BAB18-309B-42D0-B67E-B4EF8E7F7490}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/SshHandler/Form1.Designer.cs b/SshHandler/Form1.Designer.cs
new file mode 100644
index 0000000..b1e5ad7
--- /dev/null
+++ b/SshHandler/Form1.Designer.cs
@@ -0,0 +1,113 @@
+partial class Form1
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.label1 = new System.Windows.Forms.Label();
+ this.targetTextBox = new System.Windows.Forms.TextBox();
+ this.legacyCheckBox = new System.Windows.Forms.CheckBox();
+ this.connectButton = new System.Windows.Forms.Button();
+ this.cancelButton = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(12, 15);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(73, 15);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "Connect to:";
+ //
+ // targetTextBox
+ //
+ this.targetTextBox.Location = new System.Drawing.Point(15, 33);
+ this.targetTextBox.Name = "targetTextBox";
+ this.targetTextBox.Size = new System.Drawing.Size(357, 23);
+ this.targetTextBox.TabIndex = 1;
+ //
+ // legacyCheckBox
+ //
+ this.legacyCheckBox.AutoSize = true;
+ this.legacyCheckBox.Location = new System.Drawing.Point(15, 71);
+ this.legacyCheckBox.Name = "legacyCheckBox";
+ this.legacyCheckBox.Size = new System.Drawing.Size(206, 19);
+ this.legacyCheckBox.TabIndex = 2;
+ this.legacyCheckBox.Text = "Enable Legacy Mode (for old devices)";
+ this.legacyCheckBox.UseVisualStyleBackColor = true;
+ //
+ // connectButton
+ //
+ this.connectButton.Location = new System.Drawing.Point(297, 106);
+ this.connectButton.Name = "connectButton";
+ this.connectButton.Size = new System.Drawing.Size(75, 23);
+ this.connectButton.TabIndex = 3;
+ this.connectButton.Text = "Connect";
+ this.connectButton.UseVisualStyleBackColor = true;
+ this.connectButton.Click += new System.EventHandler(this.connectButton_Click);
+ //
+ // cancelButton
+ //
+ this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.cancelButton.Location = new System.Drawing.Point(216, 106);
+ this.cancelButton.Name = "cancelButton";
+ this.cancelButton.Size = new System.Drawing.Size(75, 23);
+ this.cancelButton.TabIndex = 4;
+ this.cancelButton.Text = "Cancel";
+ this.cancelButton.UseVisualStyleBackColor = true;
+ this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
+ //
+ // Form1
+ //
+ this.AcceptButton = this.connectButton;
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.cancelButton;
+ this.ClientSize = new System.Drawing.Size(384, 141);
+ this.Controls.Add(this.cancelButton);
+ this.Controls.Add(this.connectButton);
+ this.Controls.Add(this.legacyCheckBox);
+ this.Controls.Add(this.targetTextBox);
+ this.Controls.Add(this.label1);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "Form1";
+ this.ShowIcon = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "SSH Connection";
+ this.ResumeLayout(false);
+ this.PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.TextBox targetTextBox;
+ private System.Windows.Forms.CheckBox legacyCheckBox;
+ private System.Windows.Forms.Button connectButton;
+ private System.Windows.Forms.Button cancelButton;
+}
diff --git a/SshHandler/Form1.cs b/SshHandler/Form1.cs
new file mode 100644
index 0000000..90cc8c5
--- /dev/null
+++ b/SshHandler/Form1.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Windows.Forms;
+
+public partial class Form1 : Form
+{
+ // This property will store the result for the main program
+ public string FinalSshCommand { get; private set; }
+
+ public Form1(string initialTarget)
+ {
+ InitializeComponent();
+ targetTextBox.Text = initialTarget;
+ // Select the text for easy editing
+ targetTextBox.SelectAll();
+ targetTextBox.Focus();
+ }
+
+ private void connectButton_Click(object sender, EventArgs e)
+ {
+ string target = targetTextBox.Text;
+ bool useLegacy = legacyCheckBox.Checked;
+
+ string legacyArgs = "-o KexAlgorithms=+diffie-hellman-group1-sha1,diffie-hellman-group14-sha1 " +
+ "-o HostKeyAlgorithms=+ssh-rsa " +
+ "-o MACs=+hmac-sha1,hmac-sha1-96";
+
+ // Build the arguments for ssh.exe
+ string arguments = $"-A -C {(useLegacy ? legacyArgs : "")} {target}";
+
+ FinalSshCommand = arguments;
+ this.DialogResult = DialogResult.OK; // Set dialog result to OK
+ this.Close(); // Close the form
+ }
+
+ private void cancelButton_Click(object sender, EventArgs e)
+ {
+ this.DialogResult = DialogResult.Cancel; // Set dialog result to Cancel
+ this.Close();
+ }
+}
diff --git a/SshHandler/Form1.resx b/SshHandler/Form1.resx
new file mode 100644
index 0000000..5aada26
--- /dev/null
+++ b/SshHandler/Form1.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ 384, 141
+
+
+ SSH Connection
+
+
diff --git a/SshHandler/Program.cs b/SshHandler/Program.cs
new file mode 100644
index 0000000..ff98f3c
--- /dev/null
+++ b/SshHandler/Program.cs
@@ -0,0 +1,106 @@
+using Microsoft.Win32;
+using System;
+using System.Diagnostics;
+using System.Security.Principal;
+using System.Windows.Forms;
+
+static class Program
+{
+ private const string ProtocolName = "ssh";
+
+ [STAThread]
+ static void Main(string[] args)
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+
+ // If no arguments, run setup mode.
+ if (args.Length == 0)
+ {
+ SetupHandler();
+ }
+ // Otherwise, run handler mode.
+ else
+ {
+ HandleConnection(args[0]);
+ }
+ }
+
+ static void HandleConnection(string url)
+ {
+ string target = url.Replace("ssh://", "").TrimEnd('/');
+ if (!target.Contains("@"))
+ {
+ target = $"{Environment.UserName}@{target}";
+ }
+
+ // Show the form as a dialog
+ using (var form = new Form1(target))
+ {
+ if (form.ShowDialog() == DialogResult.OK)
+ {
+ // If user clicked "Connect", run the command
+ ProcessStartInfo psi = new ProcessStartInfo
+ {
+ FileName = "ssh.exe",
+ Arguments = form.FinalSshCommand,
+ UseShellExecute = true,
+ CreateNoWindow = false
+ };
+ Process.Start(psi);
+ }
+ }
+ }
+
+ static void SetupHandler()
+ {
+ if (!IsAdmin())
+ {
+ MessageBox.Show("Please run this application as an administrator to register the URL handler.", "Admin Privileges Required", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
+ // The path is simple and correct because it's a native app
+ string exePath = Application.ExecutablePath;
+ string commandStr = $"\"{exePath}\" \"%1\"";
+
+ var confirmResult = MessageBox.Show(
+ "This will register this application to handle all ssh:// links.\n\n" +
+ $"Command:\n{commandStr}\n\n" +
+ "Do you want to proceed?",
+ "Registry Setup",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Information);
+
+ if (confirmResult == DialogResult.Yes)
+ {
+ try
+ {
+ // Write the keys to HKEY_CLASSES_ROOT
+ using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(ProtocolName))
+ {
+ key.SetValue("", $"URL:{ProtocolName} Protocol");
+ key.SetValue("URL Protocol", "");
+ }
+ using (RegistryKey key = Registry.ClassesRoot.CreateSubKey($"{ProtocolName}\\shell\\open\\command"))
+ {
+ key.SetValue("", commandStr);
+ }
+ MessageBox.Show("Successfully registered ssh:// protocol handler!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Failed to write to registry:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+
+ private static bool IsAdmin()
+ {
+ using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
+ {
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+ }
+}
diff --git a/SshHandler/Properties/AssemblyInfo.cs b/SshHandler/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e69de29
diff --git a/SshHandler/SshHandler.csproj b/SshHandler/SshHandler.csproj
new file mode 100644
index 0000000..b695334
--- /dev/null
+++ b/SshHandler/SshHandler.csproj
@@ -0,0 +1,12 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ true
+ enable
+ ssh_icon.ico
+
+
+
diff --git a/SshHandler/ssh_icon.ico b/SshHandler/ssh_icon.ico
new file mode 100644
index 0000000..3334a3d
Binary files /dev/null and b/SshHandler/ssh_icon.ico differ