Compare commits

...

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
f882aaf4fc Implement dynamic tab naming for SSH/Telnet PuTTY connections
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 13:51:36 +00:00
copilot-swe-agent[bot]
d39f2b7f3c Initial plan 2026-02-20 13:42:49 +00:00
10 changed files with 125 additions and 8 deletions

View File

@@ -348,6 +348,7 @@ namespace mRemoteNG.Connection
private static void SetConnectionFormEventHandlers(ProtocolBase newProtocol, Form connectionForm)
{
newProtocol.Closed += ((ConnectionWindow)connectionForm).Prot_Event_Closed;
newProtocol.TitleChanged += ((ConnectionWindow)connectionForm).Prot_Event_TitleChanged;
}
private void SetConnectionEventHandlers(ProtocolBase newProtocol)

View File

@@ -305,6 +305,15 @@ namespace mRemoteNG.Connection.Protocol
remove => ClosedEvent = (ClosedEventHandler)Delegate.Remove(ClosedEvent, value);
}
public delegate void TitleChangedEventHandler(object sender, string newTitle);
private TitleChangedEventHandler TitleChangedEvent;
public event TitleChangedEventHandler TitleChanged
{
add => TitleChangedEvent = (TitleChangedEventHandler)Delegate.Combine(TitleChangedEvent, value);
remove => TitleChangedEvent = (TitleChangedEventHandler)Delegate.Remove(TitleChangedEvent, value);
}
public void Event_Closing(object sender)
{
@@ -336,6 +345,11 @@ namespace mRemoteNG.Connection.Protocol
ErrorOccuredEvent?.Invoke(sender, errorMsg, errorCode);
}
protected void Event_TitleChanged(object sender, string newTitle)
{
TitleChangedEvent?.Invoke(sender, newTitle);
}
protected void Event_ReconnectGroupCloseClicked()
{
Close();

View File

@@ -28,8 +28,11 @@ namespace mRemoteNG.Connection.Protocol
public class PuttyBase : ProtocolBase
{
private const int IDM_RECONF = 0x50; // PuTTY Settings Menu ID
private const int TitleMonitorIntervalMs = 500;
private bool _isPuttyNg;
private readonly DisplayProperties _display = new();
private System.Threading.Timer _titleMonitorTimer;
private string _lastWindowTitle;
#region Public Properties
@@ -333,6 +336,11 @@ namespace mRemoteNG.Connection.Protocol
Resize(this, new EventArgs());
base.Connect();
// Start monitoring PuTTY window title for dynamic tab naming
_lastWindowTitle = PuttyProcess.MainWindowTitle;
_titleMonitorTimer = new System.Threading.Timer(MonitorPuttyTitle, null, TitleMonitorIntervalMs, TitleMonitorIntervalMs);
return true;
}
catch (Exception ex)
@@ -363,6 +371,32 @@ namespace mRemoteNG.Connection.Protocol
}
}
private void MonitorPuttyTitle(object state)
{
try
{
if (PuttyProcess == null || PuttyProcess.HasExited)
{
_titleMonitorTimer?.Dispose();
return;
}
PuttyProcess.Refresh();
string currentTitle = PuttyProcess.MainWindowTitle;
if (currentTitle != _lastWindowTitle)
{
_lastWindowTitle = currentTitle;
Event_TitleChanged(this, currentTitle);
}
}
catch (Exception ex)
{
_titleMonitorTimer?.Dispose();
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg,
"PuTTY title monitoring stopped: " + ex.Message, true);
}
}
protected override void Resize(object sender, EventArgs e)
{
try
@@ -403,6 +437,9 @@ namespace mRemoteNG.Connection.Protocol
public override void Close()
{
_titleMonitorTimer?.Dispose();
_titleMonitorTimer = null;
try
{
if (PuttyProcess.HasExited == false)

View File

@@ -6117,6 +6117,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Use terminal title for tab names (SSH/Telnet).
/// </summary>
internal static string UseTerminalTitleForTabs {
get {
return ResourceManager.GetString("UseTerminalTitleForTabs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show Text.
/// </summary>

View File

@@ -1600,6 +1600,9 @@ If you run into such an error, please create a new connection file!</value>
<data name="ShowProtocolOnTabs" xml:space="preserve">
<value>Show protocols on tab names</value>
</data>
<data name="UseTerminalTitleForTabs" xml:space="preserve">
<value>Use terminal title for tab names (SSH/Telnet)</value>
</data>
<data name="SingleClickOnConnectionOpensIt" xml:space="preserve">
<value>Single click on connection opens it</value>
</data>

View File

@@ -166,5 +166,17 @@ namespace mRemoteNG.Properties {
this["BindConnectionsAndConfigPanels"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool UseTerminalTitleForTabs {
get {
return ((bool)(this["UseTerminalTitleForTabs"]));
}
set {
this["UseTerminalTitleForTabs"] = value;
}
}
}
}

View File

@@ -38,5 +38,8 @@
<Setting Name="BindConnectionsAndConfigPanels" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="UseTerminalTitleForTabs" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -41,6 +41,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowLogonInfoOnTabs = new MrngCheckBox();
chkDoubleClickClosesTab = new MrngCheckBox();
chkShowProtocolOnTabs = new MrngCheckBox();
chkUseTerminalTitleForTabs = new MrngCheckBox();
chkCreateEmptyPanelOnStart = new MrngCheckBox();
chkBindConnectionsAndConfigPanels = new MrngCheckBox();
txtBoxPanelName = new MrngTextBox();
@@ -80,7 +81,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkIdentifyQuickConnectTabs._mice = MrngCheckBox.MouseState.OUT;
chkIdentifyQuickConnectTabs.AutoSize = true;
chkIdentifyQuickConnectTabs.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkIdentifyQuickConnectTabs.Location = new System.Drawing.Point(3, 72);
chkIdentifyQuickConnectTabs.Location = new System.Drawing.Point(3, 95);
chkIdentifyQuickConnectTabs.Name = "chkIdentifyQuickConnectTabs";
chkIdentifyQuickConnectTabs.Size = new System.Drawing.Size(315, 17);
chkIdentifyQuickConnectTabs.TabIndex = 4;
@@ -104,7 +105,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkAlwaysShowPanelSelectionDlg._mice = MrngCheckBox.MouseState.OUT;
chkAlwaysShowPanelSelectionDlg.AutoSize = true;
chkAlwaysShowPanelSelectionDlg.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkAlwaysShowPanelSelectionDlg.Location = new System.Drawing.Point(3, 118);
chkAlwaysShowPanelSelectionDlg.Location = new System.Drawing.Point(3, 141);
chkAlwaysShowPanelSelectionDlg.Name = "chkAlwaysShowPanelSelectionDlg";
chkAlwaysShowPanelSelectionDlg.Size = new System.Drawing.Size(347, 17);
chkAlwaysShowPanelSelectionDlg.TabIndex = 6;
@@ -128,7 +129,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkDoubleClickClosesTab._mice = MrngCheckBox.MouseState.OUT;
chkDoubleClickClosesTab.AutoSize = true;
chkDoubleClickClosesTab.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkDoubleClickClosesTab.Location = new System.Drawing.Point(3, 95);
chkDoubleClickClosesTab.Location = new System.Drawing.Point(3, 118);
chkDoubleClickClosesTab.Name = "chkDoubleClickClosesTab";
chkDoubleClickClosesTab.Size = new System.Drawing.Size(170, 17);
chkDoubleClickClosesTab.TabIndex = 5;
@@ -147,12 +148,24 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowProtocolOnTabs.Text = "Show protocols on tab names";
chkShowProtocolOnTabs.UseVisualStyleBackColor = true;
//
// chkUseTerminalTitleForTabs
//
chkUseTerminalTitleForTabs._mice = MrngCheckBox.MouseState.OUT;
chkUseTerminalTitleForTabs.AutoSize = true;
chkUseTerminalTitleForTabs.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkUseTerminalTitleForTabs.Location = new System.Drawing.Point(3, 72);
chkUseTerminalTitleForTabs.Name = "chkUseTerminalTitleForTabs";
chkUseTerminalTitleForTabs.Size = new System.Drawing.Size(270, 17);
chkUseTerminalTitleForTabs.TabIndex = 10;
chkUseTerminalTitleForTabs.Text = "Use terminal title for tab names (SSH/Telnet)";
chkUseTerminalTitleForTabs.UseVisualStyleBackColor = true;
//
// chkCreateEmptyPanelOnStart
//
chkCreateEmptyPanelOnStart._mice = MrngCheckBox.MouseState.OUT;
chkCreateEmptyPanelOnStart.AutoSize = true;
chkCreateEmptyPanelOnStart.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 141);
chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 164);
chkCreateEmptyPanelOnStart.Name = "chkCreateEmptyPanelOnStart";
chkCreateEmptyPanelOnStart.Size = new System.Drawing.Size(271, 17);
chkCreateEmptyPanelOnStart.TabIndex = 7;
@@ -165,7 +178,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkBindConnectionsAndConfigPanels._mice = MrngCheckBox.MouseState.OUT;
chkBindConnectionsAndConfigPanels.AutoSize = true;
chkBindConnectionsAndConfigPanels.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkBindConnectionsAndConfigPanels.Location = new System.Drawing.Point(3, 210);
chkBindConnectionsAndConfigPanels.Location = new System.Drawing.Point(3, 233);
chkBindConnectionsAndConfigPanels.Name = "chkBindConnectionsAndConfigPanels";
chkBindConnectionsAndConfigPanels.Size = new System.Drawing.Size(350, 17);
chkBindConnectionsAndConfigPanels.TabIndex = 9;
@@ -175,7 +188,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// txtBoxPanelName
//
txtBoxPanelName.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
txtBoxPanelName.Location = new System.Drawing.Point(35, 177);
txtBoxPanelName.Location = new System.Drawing.Point(35, 200);
txtBoxPanelName.Name = "txtBoxPanelName";
txtBoxPanelName.Size = new System.Drawing.Size(213, 22);
txtBoxPanelName.TabIndex = 8;
@@ -183,7 +196,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// lblPanelName
//
lblPanelName.AutoSize = true;
lblPanelName.Location = new System.Drawing.Point(32, 161);
lblPanelName.Location = new System.Drawing.Point(32, 184);
lblPanelName.Name = "lblPanelName";
lblPanelName.Size = new System.Drawing.Size(69, 13);
lblPanelName.TabIndex = 9;
@@ -194,6 +207,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
pnlOptions.Controls.Add(chkAlwaysShowPanelTabs);
pnlOptions.Controls.Add(lblPanelName);
pnlOptions.Controls.Add(chkShowProtocolOnTabs);
pnlOptions.Controls.Add(chkUseTerminalTitleForTabs);
pnlOptions.Controls.Add(txtBoxPanelName);
pnlOptions.Controls.Add(chkDoubleClickClosesTab);
pnlOptions.Controls.Add(chkCreateEmptyPanelOnStart);
@@ -206,7 +220,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
pnlOptions.Dock = System.Windows.Forms.DockStyle.Top;
pnlOptions.Location = new System.Drawing.Point(0, 30);
pnlOptions.Name = "pnlOptions";
pnlOptions.Size = new System.Drawing.Size(610, 240);
pnlOptions.Size = new System.Drawing.Size(610, 263);
pnlOptions.TabIndex = 10;
//
// lblRegistrySettingsUsedInfo
@@ -243,6 +257,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
internal MrngCheckBox chkShowLogonInfoOnTabs;
internal MrngCheckBox chkDoubleClickClosesTab;
internal MrngCheckBox chkShowProtocolOnTabs;
internal MrngCheckBox chkUseTerminalTitleForTabs;
private MrngCheckBox chkCreateEmptyPanelOnStart;
private MrngCheckBox chkBindConnectionsAndConfigPanels;
private Controls.MrngTextBox txtBoxPanelName;

View File

@@ -42,6 +42,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkOpenNewTabRightOfSelected.Text = Language.OpenNewTabRight;
chkShowLogonInfoOnTabs.Text = Language.ShowLogonInfoOnTabs;
chkShowProtocolOnTabs.Text = Language.ShowProtocolOnTabs;
chkUseTerminalTitleForTabs.Text = Language.UseTerminalTitleForTabs;
chkIdentifyQuickConnectTabs.Text = Language.IdentifyQuickConnectTabs;
chkDoubleClickClosesTab.Text = Language.DoubleClickTabClosesIt;
chkAlwaysShowPanelSelectionDlg.Text = Language.AlwaysShowPanelSelection;
@@ -73,6 +74,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowLogonInfoOnTabs.Checked = Properties.OptionsTabsPanelsPage.Default.ShowLogonInfoOnTabs;
chkShowProtocolOnTabs.Checked = Properties.OptionsTabsPanelsPage.Default.ShowProtocolOnTabs;
chkUseTerminalTitleForTabs.Checked = Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs;
chkIdentifyQuickConnectTabs.Checked = Properties.OptionsTabsPanelsPage.Default.IdentifyQuickConnectTabs;
chkDoubleClickClosesTab.Checked = Properties.OptionsTabsPanelsPage.Default.DoubleClickOnTabClosesIt;
chkAlwaysShowPanelSelectionDlg.Checked = Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelSelectionDlg;
@@ -105,6 +107,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
Properties.OptionsTabsPanelsPage.Default.ShowLogonInfoOnTabs = chkShowLogonInfoOnTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.ShowProtocolOnTabs = chkShowProtocolOnTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs = chkUseTerminalTitleForTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.IdentifyQuickConnectTabs = chkIdentifyQuickConnectTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.DoubleClickOnTabClosesIt = chkDoubleClickClosesTab.Checked;
Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelSelectionDlg = chkAlwaysShowPanelSelectionDlg.Checked;

View File

@@ -832,6 +832,26 @@ namespace mRemoteNG.UI.Window
Invoke(new Action(() => tabPage.Close()));
}
public void Prot_Event_TitleChanged(object sender, string newTitle)
{
if (!Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs) return;
ProtocolBase protocolBase = sender as ProtocolBase;
if (!(protocolBase?.InterfaceControl?.Parent is ConnectionTab tabPage)) return;
if (tabPage.Disposing || tabPage.IsDisposed) return;
if (IsDisposed || Disposing) return;
string connectionName = protocolBase.InterfaceControl.Info?.Name ?? "";
string tabText = string.IsNullOrEmpty(newTitle)
? connectionName
: $"{newTitle} ({connectionName})";
tabText = tabText.Replace("&", "&&");
if (tabPage.InvokeRequired)
tabPage.Invoke(new Action(() => tabPage.TabText = tabText));
else
tabPage.TabText = tabText;
}
#endregion
}
}