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
25 changed files with 310 additions and 577 deletions

View File

@@ -16,9 +16,9 @@ mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager f
## Building the Project
### Prerequisites
- Visual Studio 2026 (version 18.4.0 or later)
- Visual Studio 2022 (version 17.14.12 or later)
- .NET 10.0 Desktop Runtime
- Windows 10/11 or Windows Server 2022+
- Windows 10/11 or Windows Server 2016+
### Build Commands
```powershell

View File

@@ -1,63 +0,0 @@
name: Remove other GitHub repo links
permissions:
contents: read
issues: write
pull-requests: write
on:
issue_comment:
types: [created, edited]
issues:
types: [opened, edited]
jobs:
filter:
runs-on: ubuntu-latest
steps:
- name: Sanitize links
uses: actions/github-script@v8
with:
script: |
const githubRepoRegex =
/\[[^\]]*\]\((?!https?:\/\/github\.com\/user-attachments\/assets\/)[^\)]*https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*\)|https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*/gi;
// CASE 1: Comment
if (context.payload.comment) {
const comment = context.payload.comment;
console.log("Processing comment:", comment.id);
const matches = comment.body.match(githubRepoRegex);
console.log("Matches:", matches);
if (!matches) return;
const sanitized = comment.body.replace(
githubRepoRegex,
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
);
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
body: sanitized
});
console.log("Comment updated");
return;
}
// CASE 2: Issue body
const issue = context.payload.issue;
if (issue) {
console.log("Processing issue:", issue.number);
const matches = issue.body?.match(githubRepoRegex);
console.log("Matches:", matches);
if (!matches) return;
const sanitized = issue.body.replace(
githubRepoRegex,
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: sanitized
});
console.log("Issue body updated");
}

View File

@@ -5,8 +5,8 @@
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.15" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76.1" />
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.14" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
<PackageVersion Include="Castle.Core" Version="5.2.1" />
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
@@ -25,7 +25,7 @@
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
@@ -62,7 +62,6 @@
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
<PackageVersion Include="System.Console" Version="4.3.1" />
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
<PackageVersion Include="System.Data.Odbc" Version="10.0.3" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />

View File

@@ -6,6 +6,7 @@
<AssemblyName>ObjectListView</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<NoWarn>$(NoWarn);WFO1000</NoWarn>
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
</PropertyGroup>

View File

@@ -1,38 +1,149 @@
using System;
using System;
using Microsoft.Data.SqlClient;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using LiteDB;
using mRemoteNG.Resources.Language;
namespace mRemoteNG.Config.DatabaseConnectors
{
//[SupportedOSPlatform("windows")]
/// <summary>
/// A helper class for testing database connectivity.
/// A helper class for testing database connectivity
/// </summary>
[SupportedOSPlatform("windows")]
///
using System;
using System.Data.SqlClient;
public class DatabaseConnectionTester
{
public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
{
try
{
using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
await dbConnector.ConnectAsync();
// Build the connection string based on the provided parameters
string connectionString = $"Data Source={server};Initial Catalog={database};User ID={username};Password={password}";
// Attempt to open a connection to the database
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
}
return ConnectionTestResult.ConnectionSucceded;
}
catch (Exception ex)
catch (SqlException ex)
{
string message = ex.Message;
if (message.Contains("server was not found", StringComparison.OrdinalIgnoreCase)
|| message.Contains("network-related", StringComparison.OrdinalIgnoreCase)
|| message.Contains("instance-specific", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.ServerNotAccessible;
if (message.Contains("Cannot open database", StringComparison.OrdinalIgnoreCase)
|| message.Contains("Unknown database", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.UnknownDatabase;
if (message.Contains("Login failed", StringComparison.OrdinalIgnoreCase)
|| message.Contains("Access denied", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.CredentialsRejected;
// Handle specific SQL exceptions
switch (ex.Number)
{
case 4060: // Invalid Database
return ConnectionTestResult.UnknownDatabase;
case 18456: // Login Failed
return ConnectionTestResult.CredentialsRejected;
case -1: // Server not accessible
return ConnectionTestResult.ServerNotAccessible;
default:
return ConnectionTestResult.UnknownError;
}
}
catch
{
// Handle any other exceptions
return ConnectionTestResult.UnknownError;
}
}
}
}
//public class DatabaseConnectionTester
//{
//public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
//{
//using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
//try
//{
// Validate architecture compatibility
//if (!Environment.Is64BitProcess)
//{
// throw new PlatformNotSupportedException("The application must run in a 64-bit process to use this database connector.");
// }
// Attempt to connect
//using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
//{
// connection.Open();
// Console.WriteLine("Connection successful!");
//}
//Console.WriteLine($"{RuntimeInformation.OSArchitecture}");
//Console.WriteLine($"{RuntimeInformation.ProcessArchitecture}");
//try
//{
// using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
// {
// connection.Open();
// Console.WriteLine("Connection successful!");
// }
//}
//catch (Exception ex)
//{
// Console.WriteLine($"Connection failed: {ex.Message}");
//}
//}
/*
try
{
using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
{
connection.Open();
}
}
catch (TypeInitializationException ex)
{
Console.WriteLine($"Type initialization error: {ex.InnerException?.Message}");
}
//await dbConnector.ConnectAsync();
return ConnectionTestResult.ConnectionSucceded;
}
catch (PlatformNotSupportedException ex)
{
// Log or handle architecture mismatch
Console.WriteLine(string.Format(Language.ErrorPlatformNotSupported, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (DllNotFoundException ex)
{
// Handle missing native dependencies
Console.WriteLine(string.Format(Language.ErrorMissingDependency, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (BadImageFormatException ex)
{
// Handle architecture mismatch in native libraries
Console.WriteLine(string.Format(Language.ErrorArchitectureMismatch, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (SqlException sqlException)
{
if (sqlException.Message.Contains("The server was not found"))
return ConnectionTestResult.ServerNotAccessible;
if (sqlException.Message.Contains("Cannot open database"))
return ConnectionTestResult.UnknownDatabase;
if (sqlException.Message.Contains("Login failed for user"))
return ConnectionTestResult.CredentialsRejected;
return ConnectionTestResult.UnknownError;
}
catch (Exception ex)
{
// Log unexpected errors
Console.WriteLine($"Unexpected error: {ex.Message}");
return ConnectionTestResult.UnknownError;
}
*/
// }
// }
}

View File

@@ -1,6 +1,5 @@
using mRemoteNG.App;
using mRemoteNG.Security.SymmetricEncryption;
using System;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.DatabaseConnectors
@@ -23,13 +22,14 @@ namespace mRemoteNG.Config.DatabaseConnectors
public static IDatabaseConnector DatabaseConnector(string type, string server, string database, string username, string password)
{
return type switch
switch (type)
{
"mysql" => new MySqlDatabaseConnector(server, database, username, password),
"odbc" => throw new NotSupportedException("ODBC database connections are not supported for schema initialization. Please use a supported database backend."),
"mssql" => new MSSqlDatabaseConnector(server, database, username, password),
_ => new MSSqlDatabaseConnector(server, database, username, password)
};
case "mysql":
return new MySqlDatabaseConnector(server, database, username, password);
case "mssql":
default:
return new MSSqlDatabaseConnector(server, database, username, password);
}
}
}
}

View File

@@ -1,106 +0,0 @@
using System.Data;
using System.Data.Common;
using System.Data.Odbc;
using System.Threading.Tasks;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.DatabaseConnectors
{
public class OdbcDatabaseConnector : IDatabaseConnector
{
private DbConnection _dbConnection { get; set; } = default(OdbcConnection);
private string _dbConnectionString = "";
private readonly string _connectionString;
private readonly string _dbUsername;
private readonly string _dbPassword;
public DbConnection DbConnection()
{
return _dbConnection;
}
public DbCommand DbCommand(string dbCommand)
{
return new OdbcCommand(dbCommand, (OdbcConnection) _dbConnection);
}
public bool IsConnected => (_dbConnection.State == ConnectionState.Open);
/// <summary>
/// Creates an ODBC database connector.
/// </summary>
/// <param name="connectionString">
/// An ODBC connection string or DSN name. If a plain DSN name is provided (no '=' character),
/// it is automatically wrapped as "DSN=&lt;name&gt;".
/// </param>
/// <param name="username">Optional user name appended to the connection string as UID.</param>
/// <param name="password">Optional password appended to the connection string as PWD.</param>
public OdbcDatabaseConnector(string connectionString, string username, string password)
{
_connectionString = connectionString;
_dbUsername = username;
_dbPassword = password;
Initialize();
}
private void Initialize()
{
BuildConnectionString();
_dbConnection = new OdbcConnection(_dbConnectionString);
}
private void BuildConnectionString()
{
// If no '=' present in the provided string it is a plain DSN name.
string baseString = _connectionString.Contains('=')
? _connectionString
: $"DSN={_connectionString}";
OdbcConnectionStringBuilder builder = new()
{
ConnectionString = baseString
};
if (!string.IsNullOrEmpty(_dbUsername))
builder["UID"] = _dbUsername;
if (!string.IsNullOrEmpty(_dbPassword))
builder["PWD"] = _dbPassword;
_dbConnectionString = builder.ConnectionString;
}
public void Connect()
{
_dbConnection.Open();
}
public async Task ConnectAsync()
{
await _dbConnection.OpenAsync();
}
public void Disconnect()
{
_dbConnection.Close();
}
public void AssociateItemToThisConnector(DbCommand dbCommand)
{
dbCommand.Connection = (OdbcConnection) _dbConnection;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool itIsSafeToFreeManagedObjects)
{
if (!itIsSafeToFreeManagedObjects) return;
_dbConnection.Close();
_dbConnection.Dispose();
}
}
}

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
@@ -39,9 +42,9 @@ namespace mRemoteNG.Connection.Protocol
public IntPtr PuttyHandle { get; set; }
private Process? PuttyProcess { get; set; }
private Process PuttyProcess { get; set; }
public static string? PuttyPath { get; set; }
public static string PuttyPath { get; set; }
public bool Focused => NativeMethods.GetForegroundWindow() == PuttyHandle;
@@ -60,7 +63,7 @@ namespace mRemoteNG.Connection.Protocol
public bool isRunning()
{
return PuttyProcess?.HasExited == false;
return !PuttyProcess.HasExited;
}
public void CreatePipe(object oData)
@@ -298,9 +301,6 @@ namespace mRemoteNG.Connection.Protocol
while (PuttyHandle.ToInt32() == 0 &
Environment.TickCount < startTicks + Properties.OptionsAdvancedPage.Default.MaxPuttyWaitTime * 1000)
{
if (PuttyProcess.HasExited)
break;
if (_isPuttyNg)
{
PuttyHandle = NativeMethods.FindWindowEx(InterfaceControl.Handle, new IntPtr(0), null, null);
@@ -308,28 +308,12 @@ namespace mRemoteNG.Connection.Protocol
else
{
PuttyProcess.Refresh();
IntPtr candidateHandle = PuttyProcess.MainWindowHandle;
if (candidateHandle != IntPtr.Zero)
{
// Check the window class name to distinguish the actual PuTTY
// terminal window ("PuTTY") from popup dialogs like the host key
// verification alert (class "#32770"). Dialogs must remain as
// top-level windows so the user can interact with them.
StringBuilder className = new(256);
NativeMethods.GetClassName(candidateHandle, className, className.Capacity);
string cls = className.ToString();
if (cls.Equals("PuTTY", StringComparison.OrdinalIgnoreCase))
{
PuttyHandle = candidateHandle;
}
}
PuttyHandle = PuttyProcess.MainWindowHandle;
}
if (PuttyHandle.ToInt32() == 0)
{
Thread.Sleep(100);
Thread.Sleep(0);
}
}
@@ -352,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)
@@ -382,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
@@ -422,25 +437,32 @@ namespace mRemoteNG.Connection.Protocol
public override void Close()
{
_titleMonitorTimer?.Dispose();
_titleMonitorTimer = null;
try
{
if (PuttyProcess?.HasExited == false)
if (PuttyProcess.HasExited == false)
{
PuttyProcess.Kill();
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyKillFailed + Environment.NewLine + ex.Message, true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyKillFailed + Environment.NewLine + ex.Message,
true);
}
try
{
PuttyProcess?.Dispose();
PuttyProcess.Dispose();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyDisposeFailed + Environment.NewLine + ex.Message, true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyDisposeFailed + Environment.NewLine + ex.Message,
true);
}
base.Close();
@@ -455,7 +477,9 @@ namespace mRemoteNG.Connection.Protocol
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyShowSettingsDialogFailed + Environment.NewLine + ex.Message, true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyShowSettingsDialogFailed + Environment.NewLine +
ex.Message, true);
}
}

View File

@@ -641,27 +641,20 @@ namespace mRemoteNG.Connection.Protocol.RDP
_rdpClient.UserName = userName;
}
// Restricted Admin and Remote Credential Guard modes use the current user's Kerberos
// credentials and do not forward explicit passwords to the remote host.
// Skipping password assignment avoids potential NTLM fallback attempts that would
// fail for accounts in the AD Protected Users security group.
if (!connectionInfo.UseRestrictedAdmin && !connectionInfo.UseRCG)
if (string.IsNullOrEmpty(password))
{
if (string.IsNullOrEmpty(password))
if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
{
if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
{
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
{
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
_rdpClient.AdvancedSettings2.ClearTextPassword = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey);
}
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
_rdpClient.AdvancedSettings2.ClearTextPassword = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey);
}
}
else
{
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
}
}
else
{
_rdpClient.AdvancedSettings2.ClearTextPassword = password;
}
if (string.IsNullOrEmpty(domain))

View File

@@ -19,7 +19,7 @@ namespace mRemoteNG.Resources.Language {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Language {
@@ -177,15 +177,6 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Sessions.
/// </summary>
internal static string _Sessions {
get {
return ResourceManager.GetString("_Sessions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Stop.
/// </summary>
@@ -2918,15 +2909,6 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Jump to Session {0}.
/// </summary>
internal static string JumpToSession {
get {
return ResourceManager.GetString("JumpToSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to (Automatically Detect).
/// </summary>
@@ -3512,15 +3494,6 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Next Session.
/// </summary>
internal static string NextSession {
get {
return ResourceManager.GetString("NextSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
@@ -3990,15 +3963,6 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Previous Session.
/// </summary>
internal static string PreviousSession {
get {
return ResourceManager.GetString("PreviousSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Printers.
/// </summary>
@@ -6153,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

@@ -120,18 +120,6 @@
<data name="MenuItem_About" xml:space="preserve">
<value>About</value>
</data>
<data name="_Sessions" xml:space="preserve">
<value>&amp;Sessions</value>
</data>
<data name="NextSession" xml:space="preserve">
<value>Next Session</value>
</data>
<data name="PreviousSession" xml:space="preserve">
<value>Previous Session</value>
</data>
<data name="JumpToSession" xml:space="preserve">
<value>Jump to Session {0}</value>
</data>
<data name="ActiveDirectory" xml:space="preserve">
<value>Active Directory</value>
</data>
@@ -1114,10 +1102,10 @@ If you run into such an error, please create a new connection file!</value>
<value>Use the Credential Security Support Provider (CredSSP) for authentication if it is available.</value>
</data>
<data name="PropertyDescriptionUseRestrictedAdmin" xml:space="preserve">
<value>Use restricted admin mode on the target host (local system context). Credentials are not forwarded to the remote host; the current user's Kerberos ticket is used instead. Recommended for AD Protected Users accounts where NTLM authentication is disabled. Requires the connecting user to have administrative rights on the target.</value>
<value>Use restricted admin mode on the target host (local system context).</value>
</data>
<data name="PropertyDescriptionUseRCG" xml:space="preserve">
<value>Use Remote Credential Guard to tunnel authentication on target back to source through the RDP channel. Kerberos requests are redirected back to the connecting device, so credentials are never sent to the remote host. Recommended for AD Protected Users accounts where NTLM authentication is disabled. Requires both client and server to be domain-joined.</value>
<value>Use Remote Credential Guard to tunnel authentication on target back to source through the RDP channel.</value>
</data>
<data name="PropertyDescriptionUser1" xml:space="preserve">
<value>Feel free to enter any information you need here.</value>
@@ -1612,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

@@ -1,4 +1,5 @@
//Generated for platform: x64
@@ -10,7 +11,7 @@ using System.Resources;
// Compute version values
//Build nr: 3405
//Build nr: 3225
// General Information
[assembly: AssemblyTitle("mRemoteNG")]
@@ -18,12 +19,12 @@ using System.Resources;
[assembly: AssemblyConfiguration("x64")]
[assembly: AssemblyCompany("Profi-KOM Ltd.")]
[assembly: AssemblyProduct("mRemoteNG Connection Manager")]
[assembly: AssemblyCopyright("(c) 2026 mRemoteNG")]
[assembly: AssemblyCopyright("(c) 2025 mRemoteNG")]
[assembly: AssemblyTrademark("Profi-KOM LTd.")]
[assembly: AssemblyCulture("")]
// Version information
[assembly: AssemblyVersion("1.78.2.3405")]
[assembly: AssemblyFileVersion("1.78.2.3405")]
[assembly: AssemblyVersion("1.78.2.3225")]
[assembly: AssemblyFileVersion("1.78.2.3225")]
[assembly: NeutralResourcesLanguageAttribute("en-US")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3405) x64")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3225) x64")]

View File

@@ -11,30 +11,14 @@
if (File.Exists(dllPath)) {
Assembly.LoadFrom(dllPath);
var sp = Host as IServiceProvider;
if (sp != null) {
// Use reflection to avoid compile-time dependency on EnvDTE
var envDteAsm = AppDomain.CurrentDomain.GetAssemblies();
Type dteType = null;
foreach (var asm in envDteAsm) {
dteType = asm.GetType("EnvDTE.DTE");
if (dteType != null) break;
}
if (dteType != null) {
var dte = sp.GetService(dteType);
if (dte != null) {
var solution = dteType.GetProperty("Solution")?.GetValue(dte);
var findItem = solution?.GetType().GetMethod("FindProjectItem", new[] { typeof(string) });
var projectItem = findItem?.Invoke(solution, new object[] { Host.TemplateFile });
var project = projectItem?.GetType().GetProperty("ContainingProject")?.GetValue(projectItem);
var cfgMgr = project?.GetType().GetProperty("ConfigurationManager")?.GetValue(project);
var activeCfg = cfgMgr?.GetType().GetProperty("ActiveConfiguration")?.GetValue(cfgMgr);
platformFromDte = activeCfg?.GetType().GetProperty("PlatformName")?.GetValue(activeCfg) as string;
}
}
}
var dte = sp?.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
var projectItem = dte?.Solution?.FindProjectItem(Host.TemplateFile);
var project = projectItem?.ContainingProject;
var cfg = project?.ConfigurationManager?.ActiveConfiguration;
platformFromDte = cfg?.PlatformName;
}
} catch {
// Fallback to environment variables below
// Log the exception if necessary
}
platformValue = platformFromDte

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

@@ -38,7 +38,6 @@ namespace mRemoteNG.UI.Forms
this.pnlDock = new WeifenLuo.WinFormsUI.Docking.DockPanel();
this.msMain = new System.Windows.Forms.MenuStrip();
this.fileMenu = new mRemoteNG.UI.Menu.FileMenu();
this.sessionsMenu = new mRemoteNG.UI.Menu.SessionsMenu();
this.viewMenu = new mRemoteNG.UI.Menu.ViewMenu();
this.toolsMenu = new mRemoteNG.UI.Menu.ToolsMenu();
this.helpMenu = new mRemoteNG.UI.Menu.HelpMenu();
@@ -76,14 +75,13 @@ namespace mRemoteNG.UI.Forms
this.msMain.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible;
this.msMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileMenu,
this.sessionsMenu,
this.viewMenu,
this.toolsMenu,
this.helpMenu});
this.msMain.Location = new System.Drawing.Point(3, 0);
this.msMain.Name = "msMain";
this.msMain.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0);
this.msMain.Size = new System.Drawing.Size(212, 25);
this.msMain.Size = new System.Drawing.Size(151, 25);
this.msMain.Stretch = false;
this.msMain.TabIndex = 0;
this.msMain.Text = "Main Toolbar";
@@ -96,13 +94,6 @@ namespace mRemoteNG.UI.Forms
this.fileMenu.Text = "&File";
this.fileMenu.TreeWindow = null;
//
// sessionsMenu
//
this.sessionsMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
this.sessionsMenu.Name = "mMenSessions";
this.sessionsMenu.Size = new System.Drawing.Size(61, 19);
this.sessionsMenu.Text = "&Sessions";
//
// viewMenu
//
this.viewMenu.FullscreenHandler = null;
@@ -262,7 +253,6 @@ namespace mRemoteNG.UI.Forms
internal System.Windows.Forms.ToolStripSeparator mMenSep3;
private System.ComponentModel.IContainer components;
private Menu.FileMenu fileMenu;
private Menu.SessionsMenu sessionsMenu;
private Menu.ViewMenu viewMenu;
private Menu.ToolsMenu toolsMenu;
private Menu.HelpMenu helpMenu;

View File

@@ -294,7 +294,6 @@ namespace mRemoteNG.UI.Forms
private void ApplyLanguage()
{
fileMenu.ApplyLanguage();
sessionsMenu.ApplyLanguage();
viewMenu.ApplyLanguage();
toolsMenu.ApplyLanguage();
helpMenu.ApplyLanguage();
@@ -700,7 +699,6 @@ namespace mRemoteNG.UI.Forms
private void PnlDock_ActiveDocumentChanged(object sender, EventArgs e)
{
ActivateConnection();
sessionsMenu.UpdateMenuState();
}
internal void UpdateWindowTitle()

View File

@@ -1,161 +0,0 @@
using System;
using System.Windows.Forms;
using mRemoteNG.UI.Window;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.UI.Menu
{
[SupportedOSPlatform("windows")]
public class SessionsMenu : ToolStripMenuItem
{
private ToolStripMenuItem _mMenSessionsNextSession;
private ToolStripMenuItem _mMenSessionsPreviousSession;
private ToolStripSeparator _mMenSessionsSep1;
private readonly ToolStripMenuItem[] _sessionNumberItems = new ToolStripMenuItem[9];
public SessionsMenu()
{
Initialize();
}
private void Initialize()
{
_mMenSessionsNextSession = new ToolStripMenuItem();
_mMenSessionsPreviousSession = new ToolStripMenuItem();
_mMenSessionsSep1 = new ToolStripSeparator();
// Initialize session number menu items (Ctrl+1 through Ctrl+9)
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i] = new ToolStripMenuItem();
}
//
// mMenSessions
//
DropDownItems.Add(_mMenSessionsNextSession);
DropDownItems.Add(_mMenSessionsPreviousSession);
DropDownItems.Add(_mMenSessionsSep1);
for (int i = 0; i < 9; i++)
{
DropDownItems.Add(_sessionNumberItems[i]);
}
Name = "mMenSessions";
Size = new System.Drawing.Size(61, 20);
Text = Language._Sessions;
//
// mMenSessionsNextSession
//
_mMenSessionsNextSession.Name = "mMenSessionsNextSession";
_mMenSessionsNextSession.ShortcutKeys = Keys.Control | Keys.Right;
_mMenSessionsNextSession.Size = new System.Drawing.Size(230, 22);
_mMenSessionsNextSession.Text = Language.NextSession;
_mMenSessionsNextSession.Click += mMenSessionsNextSession_Click;
//
// mMenSessionsPreviousSession
//
_mMenSessionsPreviousSession.Name = "mMenSessionsPreviousSession";
_mMenSessionsPreviousSession.ShortcutKeys = Keys.Control | Keys.Left;
_mMenSessionsPreviousSession.Size = new System.Drawing.Size(230, 22);
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
_mMenSessionsPreviousSession.Click += mMenSessionsPreviousSession_Click;
//
// mMenSessionsSep1
//
_mMenSessionsSep1.Name = "mMenSessionsSep1";
_mMenSessionsSep1.Size = new System.Drawing.Size(227, 6);
// Initialize session number items (Ctrl+1 through Ctrl+9)
for (int i = 0; i < 9; i++)
{
int sessionNumber = i + 1;
_sessionNumberItems[i].Name = $"mMenSessionsSession{sessionNumber}";
_sessionNumberItems[i].ShortcutKeys = Keys.Control | (Keys)((int)Keys.D1 + i);
_sessionNumberItems[i].Size = new System.Drawing.Size(230, 22);
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), sessionNumber);
_sessionNumberItems[i].Enabled = false; // Initialize as disabled
int capturedIndex = i; // Capture the index for the lambda
_sessionNumberItems[i].Click += (s, e) => JumpToSessionNumber(capturedIndex);
}
// Initialize navigation items as disabled
_mMenSessionsNextSession.Enabled = false;
_mMenSessionsPreviousSession.Enabled = false;
// Hook up the dropdown opening event to update enabled state
DropDownOpening += SessionsMenu_DropDownOpening;
}
public void ApplyLanguage()
{
Text = Language._Sessions;
_mMenSessionsNextSession.Text = Language.NextSession;
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), i + 1);
}
}
public void UpdateMenuState()
{
// Update enabled state of menu items based on active sessions
var connectionWindow = GetActiveConnectionWindow();
bool hasMultipleSessions = false;
int sessionCount = 0;
if (connectionWindow != null)
{
var documents = connectionWindow.GetDocuments();
sessionCount = documents.Length;
hasMultipleSessions = sessionCount > 1;
}
_mMenSessionsNextSession.Enabled = hasMultipleSessions;
_mMenSessionsPreviousSession.Enabled = hasMultipleSessions;
// Enable/disable session number items based on session count
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i].Enabled = (i < sessionCount);
}
}
private void SessionsMenu_DropDownOpening(object sender, EventArgs e)
{
// Update state when menu is opened (for visual feedback)
UpdateMenuState();
}
private void mMenSessionsNextSession_Click(object sender, EventArgs e)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToNextTab();
}
private void mMenSessionsPreviousSession_Click(object sender, EventArgs e)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToPreviousTab();
}
private void JumpToSessionNumber(int index)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToTab(index);
}
private ConnectionWindow GetActiveConnectionWindow()
{
return FrmMain.Default.pnlDock?.ActiveDocument as ConnectionWindow;
}
}
}

View File

@@ -387,34 +387,6 @@ namespace mRemoteNG.UI.Window
}
}
internal void NavigateToTab(int index)
{
try
{
var documents = connDock.DocumentsToArray();
if (index < 0 || index >= documents.Length) return;
documents[index].DockHandler.Activate();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("NavigateToTab (UI.Window.ConnectionWindow) failed", ex);
}
}
internal IDockContent[] GetDocuments()
{
try
{
return connDock.DocumentsToArray();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("GetDocuments (UI.Window.ConnectionWindow) failed", ex);
return Array.Empty<IDockContent>();
}
}
#endregion
#region Events
@@ -860,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
}
}

View File

@@ -9,9 +9,6 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<TransformOnBuild>true</TransformOnBuild>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<ApplicationIcon>Icons\mRemoteNG.ico</ApplicationIcon>
<Version>1.78.2-dev</Version>
<Description>Multi-protocol remote connections manager</Description>
@@ -141,7 +138,6 @@
<PackageReference Include="Microsoft.Web.WebView2" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="MySql.Data" />
<PackageReference Include="System.Data.Odbc" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Newtonsoft.Json.Schema" />
<PackageReference Include="OpenCover" />
@@ -281,11 +277,6 @@
<SubType>UserControl</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<T4ParameterValues Include="platformType">
<Value>$(Platform)</Value>
</T4ParameterValues>
</ItemGroup>
<ItemGroup>
<None Update="Properties\AssemblyInfo.tt">
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
@@ -553,10 +544,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="145.0.26" />
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="144.0.12" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='arm64'">
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="145.0.26" />
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="144.0.12" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
@@ -596,5 +587,4 @@
<RemoveDir Directories="$(TempBuildDir)" />
<RemoveDir Directories="$(ProjectDir)bin\x64\Release Self-Contained" />
</Target>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v18.0\TextTemplating\Microsoft.TextTemplating.targets" />
</Project>

View File

@@ -73,35 +73,3 @@ Connections
- Move Up
* - Ctrl+Down
- Move Down
Sessions
========
.. list-table::
:widths: 30 70
:header-rows: 1
* - Keybinding
- Action
* - Ctrl+Right
- Next Session/Tab
* - Ctrl+Left
- Previous Session/Tab
* - Ctrl+1
- Jump to Session 1
* - Ctrl+2
- Jump to Session 2
* - Ctrl+3
- Jump to Session 3
* - Ctrl+4
- Jump to Session 4
* - Ctrl+5
- Jump to Session 5
* - Ctrl+6
- Jump to Session 6
* - Ctrl+7
- Jump to Session 7
* - Ctrl+8
- Jump to Session 8
* - Ctrl+9
- Jump to Session 9