Compare commits

...

24 Commits

Author SHA1 Message Date
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
14 changed files with 419 additions and 140 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

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

@@ -0,0 +1,38 @@
name: Remove GitHub repo links
on:
issue_comment:
types: [created, edited]
issues:
types: [opened, edited]
jobs:
filter:
runs-on: ubuntu-latest
steps:
- name: Replace GitHub repo links in comments
uses: actions/github-script@v8
with:
script: |
const comment = context.payload.comment;
if (!comment) return;
const body = comment.body;
// Regex for GitHub repo links
const githubRepoRegex = /https?:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:[^\s]*)?/gi;
if (githubRepoRegex.test(body)) {
const sanitized = body.replace(
githubRepoRegex,
"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
});
}

View File

@@ -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

@@ -2559,4 +2559,16 @@ Nightly Channel includes Alphas, Betas &amp; Release Candidates.</value>
<data name="VaultOpenbaoSecretEngineSSHOTP" xml:space="preserve">
<value>SSH engine OTP mode</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>
</root>

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, 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, 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

View File

@@ -138,6 +138,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" />

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