diff --git a/mRemoteNG/Connection/ConnectionInitiator.cs b/mRemoteNG/Connection/ConnectionInitiator.cs index c0ab7604c..c18bf5ec4 100644 --- a/mRemoteNG/Connection/ConnectionInitiator.cs +++ b/mRemoteNG/Connection/ConnectionInitiator.cs @@ -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) diff --git a/mRemoteNG/Connection/Protocol/ProtocolBase.cs b/mRemoteNG/Connection/Protocol/ProtocolBase.cs index 6664274a1..3835a85e3 100644 --- a/mRemoteNG/Connection/Protocol/ProtocolBase.cs +++ b/mRemoteNG/Connection/Protocol/ProtocolBase.cs @@ -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(); diff --git a/mRemoteNG/Connection/Protocol/PuttyBase.cs b/mRemoteNG/Connection/Protocol/PuttyBase.cs index 0ac453446..fe71f7bda 100644 --- a/mRemoteNG/Connection/Protocol/PuttyBase.cs +++ b/mRemoteNG/Connection/Protocol/PuttyBase.cs @@ -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) diff --git a/mRemoteNG/Language/Language.Designer.cs b/mRemoteNG/Language/Language.Designer.cs index 92979144c..eb1abbe35 100644 --- a/mRemoteNG/Language/Language.Designer.cs +++ b/mRemoteNG/Language/Language.Designer.cs @@ -6117,6 +6117,15 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to Use terminal title for tab names (SSH/Telnet). + /// + internal static string UseTerminalTitleForTabs { + get { + return ResourceManager.GetString("UseTerminalTitleForTabs", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show Text. /// diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 8b08a641f..acdd83bec 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -1600,6 +1600,9 @@ If you run into such an error, please create a new connection file! Show protocols on tab names + + Use terminal title for tab names (SSH/Telnet) + Single click on connection opens it diff --git a/mRemoteNG/Properties/OptionsTabsPanelsPage.Designer.cs b/mRemoteNG/Properties/OptionsTabsPanelsPage.Designer.cs index b14cd22c3..89da0bff8 100644 --- a/mRemoteNG/Properties/OptionsTabsPanelsPage.Designer.cs +++ b/mRemoteNG/Properties/OptionsTabsPanelsPage.Designer.cs @@ -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; + } + } } } diff --git a/mRemoteNG/Properties/OptionsTabsPanelsPage.settings b/mRemoteNG/Properties/OptionsTabsPanelsPage.settings index 2f7159637..32d9f2316 100644 --- a/mRemoteNG/Properties/OptionsTabsPanelsPage.settings +++ b/mRemoteNG/Properties/OptionsTabsPanelsPage.settings @@ -38,5 +38,8 @@ False + + False + \ No newline at end of file diff --git a/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs b/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs index d20ca2325..ccbbd31fd 100644 --- a/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs +++ b/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs @@ -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; diff --git a/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.cs b/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.cs index c7f52e365..142f4b40f 100644 --- a/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.cs +++ b/mRemoteNG/UI/Forms/OptionsPages/TabsPanelsPage.cs @@ -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; diff --git a/mRemoteNG/UI/Window/ConnectionWindow.cs b/mRemoteNG/UI/Window/ConnectionWindow.cs index 4fc122e3d..63332e69d 100644 --- a/mRemoteNG/UI/Window/ConnectionWindow.cs +++ b/mRemoteNG/UI/Window/ConnectionWindow.cs @@ -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 } }