Compare commits

..

48 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5b59518364 Fix RDP connections for AD Protected Users by supporting Kerberos-only modes
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-25 17:37:30 +00:00
copilot-swe-agent[bot]
ba72c1666c Initial plan 2026-02-25 17:28:12 +00:00
Dimitrij
43735b1d04 Merge pull request #3169 from mRemoteNG/renovate/vstest-monorepo
Update dependency Microsoft.NET.Test.Sdk to 18.3.0
2026-02-24 17:25:53 +00:00
renovate[bot]
002f6cb290 Update dependency Microsoft.NET.Test.Sdk to 18.3.0 2026-02-24 13:30:48 +00:00
Dimitrij
92c617d442 Merge pull request #3168 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-24 10:20:28 +00:00
renovate[bot]
764b96f864 Update aws-sdk-net monorepo 2026-02-23 21:57:17 +00:00
Dimitrij
e30a8ad3f0 Merge pull request #3164 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-145.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v145
2026-02-22 16:26:38 +00:00
Dimitrij
1e85969e3a Merge pull request #3165 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-145.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v145
2026-02-22 16:26:24 +00:00
renovate[bot]
1a47bba982 Update dependency chromiumembeddedframework.runtime.win-x64 to v145 2026-02-22 16:24:37 +00:00
renovate[bot]
1cc5f05bf5 Update dependency chromiumembeddedframework.runtime.win-arm64 to v145 2026-02-22 16:24:32 +00:00
Dimitrij
7e0277f85d Update GitHub regex to exclude user-attachments
allow attachments
2026-02-22 16:24:08 +00:00
Kvarkas
ff3da2c7d5 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-22 00:19:37 +00:00
Kvarkas
c90152c35c small fixes 2026-02-22 00:19:24 +00:00
Dimitrij
95e20afefa Update regex to filter GitHub repo links 2026-02-21 21:37:06 +00:00
Dimitrij
4c7b3a9da5 Censor external GitHub repository links in comments 2026-02-21 21:30:52 +00:00
Dimitrij
14ec6b38e8 Fix regex replacement for issue body sanitization 2026-02-21 21:29:22 +00:00
Dimitrij
f10bcdee20 Censor external GitHub links in comments and issues 2026-02-21 21:22:40 +00:00
Dimitrij
af105a62f1 Refactor GitHub link sanitization in workflow 2026-02-21 21:19:06 +00:00
Dimitrij
5173eec41d Enhance logging in filter-links workflow
Add logging for event name and payload keys in GitHub Actions workflow.
2026-02-21 21:12:12 +00:00
Dimitrij
797a8dff14 Add read permission for contents in workflow 2026-02-21 21:10:27 +00:00
Dimitrij
7dcc43824a Fix typo in console log statement 2026-02-21 21:09:11 +00:00
Dimitrij
9b04d70276 Correct typo in logging comment context
Fix typo in console log statement for comment context.
2026-02-21 21:07:15 +00:00
Dimitrij
21407c0805 Improve comment processing in filter-links workflow
Refactor comment handling and logging for GitHub repo link filtering.
2026-02-21 20:59:36 +00:00
Dimitrij
286eee370a Update workflow to include permissions
Add permissions for issues and pull requests.
2026-02-21 20:53:40 +00:00
Dimitrij
bd4c9fea34 Merge pull request #3159 from mRemoteNG/renovate/actions-github-script-8.x
Update actions/github-script action to v8
2026-02-20 16:03:49 +00:00
renovate[bot]
9ba21e9047 Update actions/github-script action to v8 2026-02-20 15:02:59 +00:00
Dimitrij
2e7ec5a354 Merge pull request #3141 from mRemoteNG/copilot/add-keyboard-shortcuts-sessions
Add keyboard shortcuts for session/tab navigation
2026-02-20 15:02:00 +00:00
copilot-swe-agent[bot]
d83be65e5c Fix menu state management and key calculation
- Initialize all menu items as disabled by default
- Add public UpdateMenuState() method for reliable state updates
- Wire UpdateMenuState to ActiveDocumentChanged event in FrmMain
- Fix key calculation to use explicit cast: (Keys)((int)Keys.D1 + i)

This ensures shortcuts are only active when appropriate sessions exist,
preventing global interception in text boxes and other contexts.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:54:22 +00:00
Dimitrij
fec445d3a5 Merge pull request #3158 from mRemoteNG/copilot/fix-remote-code-execution-issue
Remove EnableUnsafeBinaryFormatterSerialization from ObjectListView
2026-02-20 14:51:46 +00:00
Kvarkas
87fb45c115 odbc fixes 2026-02-20 14:50:39 +00:00
copilot-swe-agent[bot]
779b541702 Remove EnableUnsafeBinaryFormatterSerialization from ObjectListView project
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:48:48 +00:00
copilot-swe-agent[bot]
24d2a0b407 Initial plan 2026-02-20 14:40:56 +00:00
Kvarkas
da10db53ee Add links filtering flow 2026-02-20 14:39:48 +00:00
Kvarkas
4487545445 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-20 14:34:18 +00:00
Dimitrij
afbec5fae0 Merge pull request #3157 from mRemoteNG/renovate/major-dotnet-monorepo
Update dependency System.Data.Odbc to v10
2026-02-20 14:33:55 +00:00
Dimitrij
6c20941430 Merge branch 'v1.78.2-dev' into renovate/major-dotnet-monorepo 2026-02-20 14:33:46 +00:00
Dimitrij
8f79b313c5 Merge pull request #3156 from mRemoteNG/renovate/dotnet-monorepo
Update dependency System.Data.Odbc to 9.0.13
2026-02-20 14:33:10 +00:00
Kvarkas
295fcaae12 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-20 14:29:33 +00:00
Kvarkas
3ffac2684c upd instructions 2026-02-20 14:29:22 +00:00
renovate[bot]
26978fc09d Update dependency System.Data.Odbc to v10 2026-02-20 14:29:05 +00:00
renovate[bot]
151d82a640 Update dependency System.Data.Odbc to 9.0.13 2026-02-20 14:29:00 +00:00
Dimitrij
12b4afac9a Merge pull request #3155 from mRemoteNG/copilot/add-odbc-support
Add ODBC support as a database backend for connection list storage
2026-02-20 14:28:03 +00:00
Dimitrij
12f2942202 Update mRemoteNG/Config/DatabaseConnectors/DatabaseConnectorFactory.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 14:27:04 +00:00
copilot-swe-agent[bot]
0b7fd5b5f5 Add ODBC support for database connection list storage
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:02:53 +00:00
copilot-swe-agent[bot]
fd41e27d47 Initial plan 2026-02-20 13:54:30 +00:00
copilot-swe-agent[bot]
9ba3cf0727 Add keyboard shortcuts documentation for session navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-13 10:37:15 +00:00
copilot-swe-agent[bot]
71911bba7b Add Sessions menu with keyboard shortcuts for tab navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-13 10:36:32 +00:00
copilot-swe-agent[bot]
e9f94cbe31 Initial plan 2026-02-13 10:32:08 +00:00
25 changed files with 577 additions and 310 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 2022 (version 17.14.12 or later)
- Visual Studio 2026 (version 18.4.0 or later)
- .NET 10.0 Desktop Runtime
- Windows 10/11 or Windows Server 2016+
- Windows 10/11 or Windows Server 2022+
### Build Commands
```powershell

63
.github/workflows/filter-links.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
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.14" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76" />
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.15" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76.1" />
<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.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<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,6 +62,7 @@
<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,7 +6,6 @@
<AssemblyName>ObjectListView</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<NoWarn>$(NoWarn);WFO1000</NoWarn>
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
</PropertyGroup>

View File

@@ -1,149 +1,38 @@
using System;
using Microsoft.Data.SqlClient;
using System;
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>
///
using System;
using System.Data.SqlClient;
[SupportedOSPlatform("windows")]
public class DatabaseConnectionTester
{
public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
{
try
{
// 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();
}
using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
await dbConnector.ConnectAsync();
return ConnectionTestResult.ConnectionSucceded;
}
catch (SqlException ex)
catch (Exception ex)
{
// 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
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;
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,5 +1,6 @@
using mRemoteNG.App;
using mRemoteNG.Security.SymmetricEncryption;
using System;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.DatabaseConnectors
@@ -22,14 +23,13 @@ namespace mRemoteNG.Config.DatabaseConnectors
public static IDatabaseConnector DatabaseConnector(string type, string server, string database, string username, string password)
{
switch (type)
return type switch
{
case "mysql":
return new MySqlDatabaseConnector(server, database, username, password);
case "mssql":
default:
return new MSSqlDatabaseConnector(server, database, username, password);
}
"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)
};
}
}
}

View File

@@ -0,0 +1,106 @@
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,7 +348,6 @@ 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,15 +305,6 @@ 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)
{
@@ -345,11 +336,6 @@ 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,11 +28,8 @@ 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
@@ -42,9 +39,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;
@@ -63,7 +60,7 @@ namespace mRemoteNG.Connection.Protocol
public bool isRunning()
{
return !PuttyProcess.HasExited;
return PuttyProcess?.HasExited == false;
}
public void CreatePipe(object oData)
@@ -301,6 +298,9 @@ 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,12 +308,28 @@ namespace mRemoteNG.Connection.Protocol
else
{
PuttyProcess.Refresh();
PuttyHandle = PuttyProcess.MainWindowHandle;
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;
}
}
}
if (PuttyHandle.ToInt32() == 0)
{
Thread.Sleep(0);
Thread.Sleep(100);
}
}
@@ -336,11 +352,6 @@ 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)
@@ -371,32 +382,6 @@ 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
@@ -437,32 +422,25 @@ 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();
@@ -477,9 +455,7 @@ 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,20 +641,27 @@ namespace mRemoteNG.Connection.Protocol.RDP
_rdpClient.UserName = userName;
}
if (string.IsNullOrEmpty(password))
// 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 (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
if (string.IsNullOrEmpty(password))
{
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
if (Properties.OptionsCredentialsPage.Default.EmptyCredentials == "custom")
{
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
_rdpClient.AdvancedSettings2.ClearTextPassword = cryptographyProvider.Decrypt(Properties.OptionsCredentialsPage.Default.DefaultPassword, Runtime.EncryptionKey);
if (Properties.OptionsCredentialsPage.Default.DefaultPassword != "")
{
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", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Language {
@@ -177,6 +177,15 @@ 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>
@@ -2909,6 +2918,15 @@ 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>
@@ -3494,6 +3512,15 @@ 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>
@@ -3963,6 +3990,15 @@ 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>
@@ -6117,15 +6153,6 @@ 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,6 +120,18 @@
<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>
@@ -1102,10 +1114,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).</value>
<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>
</data>
<data name="PropertyDescriptionUseRCG" xml:space="preserve">
<value>Use Remote Credential Guard to tunnel authentication on target back to source through the RDP channel.</value>
<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>
</data>
<data name="PropertyDescriptionUser1" xml:space="preserve">
<value>Feel free to enter any information you need here.</value>
@@ -1600,9 +1612,6 @@ 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,5 +1,4 @@
//Generated for platform: x64
@@ -11,7 +10,7 @@ using System.Resources;
// Compute version values
//Build nr: 3225
//Build nr: 3405
// General Information
[assembly: AssemblyTitle("mRemoteNG")]
@@ -19,12 +18,12 @@ using System.Resources;
[assembly: AssemblyConfiguration("x64")]
[assembly: AssemblyCompany("Profi-KOM Ltd.")]
[assembly: AssemblyProduct("mRemoteNG Connection Manager")]
[assembly: AssemblyCopyright("(c) 2025 mRemoteNG")]
[assembly: AssemblyCopyright("(c) 2026 mRemoteNG")]
[assembly: AssemblyTrademark("Profi-KOM LTd.")]
[assembly: AssemblyCulture("")]
// Version information
[assembly: AssemblyVersion("1.78.2.3225")]
[assembly: AssemblyFileVersion("1.78.2.3225")]
[assembly: AssemblyVersion("1.78.2.3405")]
[assembly: AssemblyFileVersion("1.78.2.3405")]
[assembly: NeutralResourcesLanguageAttribute("en-US")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3225) x64")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3405) x64")]

View File

@@ -11,14 +11,30 @@
if (File.Exists(dllPath)) {
Assembly.LoadFrom(dllPath);
var sp = Host as IServiceProvider;
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;
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;
}
}
}
}
} catch {
// Log the exception if necessary
// Fallback to environment variables below
}
platformValue = platformFromDte

View File

@@ -166,17 +166,5 @@ 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,8 +38,5 @@
<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,7 +41,6 @@ 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();
@@ -81,7 +80,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, 95);
chkIdentifyQuickConnectTabs.Location = new System.Drawing.Point(3, 72);
chkIdentifyQuickConnectTabs.Name = "chkIdentifyQuickConnectTabs";
chkIdentifyQuickConnectTabs.Size = new System.Drawing.Size(315, 17);
chkIdentifyQuickConnectTabs.TabIndex = 4;
@@ -105,7 +104,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, 141);
chkAlwaysShowPanelSelectionDlg.Location = new System.Drawing.Point(3, 118);
chkAlwaysShowPanelSelectionDlg.Name = "chkAlwaysShowPanelSelectionDlg";
chkAlwaysShowPanelSelectionDlg.Size = new System.Drawing.Size(347, 17);
chkAlwaysShowPanelSelectionDlg.TabIndex = 6;
@@ -129,7 +128,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, 118);
chkDoubleClickClosesTab.Location = new System.Drawing.Point(3, 95);
chkDoubleClickClosesTab.Name = "chkDoubleClickClosesTab";
chkDoubleClickClosesTab.Size = new System.Drawing.Size(170, 17);
chkDoubleClickClosesTab.TabIndex = 5;
@@ -148,24 +147,12 @@ 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, 164);
chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 141);
chkCreateEmptyPanelOnStart.Name = "chkCreateEmptyPanelOnStart";
chkCreateEmptyPanelOnStart.Size = new System.Drawing.Size(271, 17);
chkCreateEmptyPanelOnStart.TabIndex = 7;
@@ -178,7 +165,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, 233);
chkBindConnectionsAndConfigPanels.Location = new System.Drawing.Point(3, 210);
chkBindConnectionsAndConfigPanels.Name = "chkBindConnectionsAndConfigPanels";
chkBindConnectionsAndConfigPanels.Size = new System.Drawing.Size(350, 17);
chkBindConnectionsAndConfigPanels.TabIndex = 9;
@@ -188,7 +175,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, 200);
txtBoxPanelName.Location = new System.Drawing.Point(35, 177);
txtBoxPanelName.Name = "txtBoxPanelName";
txtBoxPanelName.Size = new System.Drawing.Size(213, 22);
txtBoxPanelName.TabIndex = 8;
@@ -196,7 +183,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// lblPanelName
//
lblPanelName.AutoSize = true;
lblPanelName.Location = new System.Drawing.Point(32, 184);
lblPanelName.Location = new System.Drawing.Point(32, 161);
lblPanelName.Name = "lblPanelName";
lblPanelName.Size = new System.Drawing.Size(69, 13);
lblPanelName.TabIndex = 9;
@@ -207,7 +194,6 @@ 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);
@@ -220,7 +206,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, 263);
pnlOptions.Size = new System.Drawing.Size(610, 240);
pnlOptions.TabIndex = 10;
//
// lblRegistrySettingsUsedInfo
@@ -257,7 +243,6 @@ 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,7 +42,6 @@ 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;
@@ -74,7 +73,6 @@ 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;
@@ -107,7 +105,6 @@ 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,6 +38,7 @@ 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();
@@ -75,13 +76,14 @@ 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(151, 25);
this.msMain.Size = new System.Drawing.Size(212, 25);
this.msMain.Stretch = false;
this.msMain.TabIndex = 0;
this.msMain.Text = "Main Toolbar";
@@ -94,6 +96,13 @@ 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;
@@ -253,6 +262,7 @@ 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,6 +294,7 @@ namespace mRemoteNG.UI.Forms
private void ApplyLanguage()
{
fileMenu.ApplyLanguage();
sessionsMenu.ApplyLanguage();
viewMenu.ApplyLanguage();
toolsMenu.ApplyLanguage();
helpMenu.ApplyLanguage();
@@ -699,6 +700,7 @@ namespace mRemoteNG.UI.Forms
private void PnlDock_ActiveDocumentChanged(object sender, EventArgs e)
{
ActivateConnection();
sessionsMenu.UpdateMenuState();
}
internal void UpdateWindowTitle()

View File

@@ -0,0 +1,161 @@
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,6 +387,34 @@ 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
@@ -832,26 +860,6 @@ 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,6 +9,9 @@
<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>
@@ -138,6 +141,7 @@
<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" />
@@ -277,6 +281,11 @@
<SubType>UserControl</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<T4ParameterValues Include="platformType">
<Value>$(Platform)</Value>
</T4ParameterValues>
</ItemGroup>
<ItemGroup>
<None Update="Properties\AssemblyInfo.tt">
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
@@ -544,10 +553,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="144.0.12" />
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="145.0.26" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='arm64'">
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="144.0.12" />
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="145.0.26" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
@@ -587,4 +596,5 @@
<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,3 +73,35 @@ 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