mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-26 03:58:45 +08:00
Compare commits
40 Commits
copilot/fi
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c421c9876b | ||
|
|
5db7fadefe | ||
|
|
4a1d75d189 | ||
|
|
ff3da2c7d5 | ||
|
|
c90152c35c | ||
|
|
95e20afefa | ||
|
|
4c7b3a9da5 | ||
|
|
14ec6b38e8 | ||
|
|
f10bcdee20 | ||
|
|
af105a62f1 | ||
|
|
5173eec41d | ||
|
|
797a8dff14 | ||
|
|
7dcc43824a | ||
|
|
9b04d70276 | ||
|
|
21407c0805 | ||
|
|
286eee370a | ||
|
|
bd4c9fea34 | ||
|
|
9ba21e9047 | ||
|
|
2e7ec5a354 | ||
|
|
d83be65e5c | ||
|
|
fec445d3a5 | ||
|
|
87fb45c115 | ||
|
|
779b541702 | ||
|
|
24d2a0b407 | ||
|
|
da10db53ee | ||
|
|
4487545445 | ||
|
|
afbec5fae0 | ||
|
|
6c20941430 | ||
|
|
8f79b313c5 | ||
|
|
295fcaae12 | ||
|
|
3ffac2684c | ||
|
|
26978fc09d | ||
|
|
151d82a640 | ||
|
|
12b4afac9a | ||
|
|
12f2942202 | ||
|
|
0b7fd5b5f5 | ||
|
|
fd41e27d47 | ||
|
|
9ba3cf0727 | ||
|
|
71911bba7b | ||
|
|
e9f94cbe31 |
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -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
63
.github/workflows/filter-links.yml
vendored
Normal 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\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*\)|https?:\/\/github\.com\/[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");
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<AssemblyName>ObjectListView</AssemblyName>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
|
||||
<NoWarn>$(NoWarn);WFO1000</NoWarn>
|
||||
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
106
mRemoteNG/Config/DatabaseConnectors/OdbcDatabaseConnector.cs
Normal file
106
mRemoteNG/Config/DatabaseConnectors/OdbcDatabaseConnector.cs
Normal 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=<name>".
|
||||
/// </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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,20 @@ namespace mRemoteNG.Connection
|
||||
[Browsable(false)]
|
||||
public bool PleaseConnect { get; set; }
|
||||
|
||||
private HostStatus _hostStatus = HostStatus.Unknown;
|
||||
|
||||
[Browsable(false)]
|
||||
public HostStatus HostStatus
|
||||
{
|
||||
get => _hostStatus;
|
||||
set
|
||||
{
|
||||
if (_hostStatus == value) return;
|
||||
_hostStatus = value;
|
||||
RaisePropertyChangedEvent(this, new PropertyChangedEventArgs(nameof(HostStatus)));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
9
mRemoteNG/Connection/HostStatus.cs
Normal file
9
mRemoteNG/Connection/HostStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace mRemoteNG.Connection
|
||||
{
|
||||
public enum HostStatus
|
||||
{
|
||||
Unknown,
|
||||
Online,
|
||||
Offline
|
||||
}
|
||||
}
|
||||
@@ -39,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;
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace mRemoteNG.Connection.Protocol
|
||||
|
||||
public bool isRunning()
|
||||
{
|
||||
return !PuttyProcess.HasExited;
|
||||
return PuttyProcess?.HasExited == false;
|
||||
}
|
||||
|
||||
public void CreatePipe(object oData)
|
||||
@@ -298,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);
|
||||
@@ -305,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,27 +424,23 @@ namespace mRemoteNG.Connection.Protocol
|
||||
{
|
||||
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();
|
||||
@@ -440,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using mRemoteNG.App;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using mRemoteNG.Security;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.UI.Forms;
|
||||
using mRemoteNG.Resources.Language;
|
||||
using System.Runtime.Versioning;
|
||||
using mRemoteNG.Security;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
// ReSharper disable ArrangeAccessorOwnerBody
|
||||
|
||||
@@ -35,7 +33,6 @@ namespace mRemoteNG.Connection.Protocol.VNC
|
||||
|
||||
public ProtocolVNC()
|
||||
{
|
||||
PatchVncKeyTranslationTable();
|
||||
Control = new VncSharpCore.RemoteDesktop();
|
||||
}
|
||||
|
||||
@@ -218,39 +215,6 @@ namespace mRemoteNG.Connection.Protocol.VNC
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _keyTablePatched = false;
|
||||
private static readonly object _patchLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Patches the VncSharpCore KeyTranslationTable to add a missing entry for the Caps Lock key.
|
||||
/// Without this, pressing Caps Lock sends a 't' keypress to the remote because ToAscii()
|
||||
/// incorrectly maps VK_CAPITAL (0x14) when it is absent from the translation table.
|
||||
/// </summary>
|
||||
private static void PatchVncKeyTranslationTable()
|
||||
{
|
||||
lock (_patchLock)
|
||||
{
|
||||
if (_keyTablePatched) return;
|
||||
try
|
||||
{
|
||||
var tableField = typeof(VncSharpCore.RemoteDesktop)
|
||||
.GetField("KeyTranslationTable", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (tableField?.GetValue(null) is Dictionary<int, int> table)
|
||||
{
|
||||
const int VK_CAPITAL = 0x14; // Windows virtual key code for Caps Lock
|
||||
const int XK_Caps_Lock = 0xFF20; // X11 keysym for Caps Lock
|
||||
table.TryAdd(VK_CAPITAL, XK_Caps_Lock);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Runtime.MessageCollector.AddMessage(Messages.MessageClass.WarningMsg,
|
||||
$"Failed to patch VncSharpCore KeyTranslationTable for Caps Lock fix: {ex.Message}", true);
|
||||
}
|
||||
_keyTablePatched = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Events & Handlers
|
||||
|
||||
47
mRemoteNG/Language/Language.Designer.cs
generated
47
mRemoteNG/Language/Language.Designer.cs
generated
@@ -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 &Sessions.
|
||||
/// </summary>
|
||||
internal static string _Sessions {
|
||||
get {
|
||||
return ResourceManager.GetString("_Sessions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to &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>
|
||||
@@ -6063,6 +6099,15 @@ namespace mRemoteNG.Resources.Language {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show host status indicator in connection tree.
|
||||
/// </summary>
|
||||
internal static string ShowStatusIndicatorInTree {
|
||||
get {
|
||||
return ResourceManager.GetString("ShowStatusIndicatorInTree", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to &Show Help Text.
|
||||
/// </summary>
|
||||
|
||||
@@ -120,6 +120,18 @@
|
||||
<data name="MenuItem_About" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="_Sessions" xml:space="preserve">
|
||||
<value>&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>
|
||||
@@ -1594,6 +1606,9 @@ If you run into such an error, please create a new connection file!</value>
|
||||
<data name="ShowFullConsFilePath" xml:space="preserve">
|
||||
<value>Show full connections file path in window title</value>
|
||||
</data>
|
||||
<data name="ShowStatusIndicatorInTree" xml:space="preserve">
|
||||
<value>Show host status indicator in connection tree</value>
|
||||
</data>
|
||||
<data name="ShowLogonInfoOnTabs" xml:space="preserve">
|
||||
<value>Show logon information on tab names</value>
|
||||
</data>
|
||||
|
||||
@@ -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")]
|
||||
@@ -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
|
||||
|
||||
@@ -94,5 +94,17 @@ namespace mRemoteNG.Properties {
|
||||
this["cbAppearancePageInOptionMenu"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool ShowStatusIndicatorInTree {
|
||||
get {
|
||||
return ((bool)(this["ShowStatusIndicatorInTree"]));
|
||||
}
|
||||
set {
|
||||
this["ShowStatusIndicatorInTree"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,8 @@
|
||||
<Setting Name="cbAppearancePageInOptionMenu" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="ShowStatusIndicatorInTree" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
133
mRemoteNG/Tools/ConnectionStatusChecker.cs
Normal file
133
mRemoteNG/Tools/ConnectionStatusChecker.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using mRemoteNG.Connection;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Tree;
|
||||
|
||||
namespace mRemoteNG.Tools
|
||||
{
|
||||
public class ConnectionStatusChecker : IDisposable
|
||||
{
|
||||
private Timer _timer;
|
||||
private ConnectionTreeModel _model;
|
||||
private bool _disposed;
|
||||
private const int CheckIntervalMs = 30000;
|
||||
private const int PingTimeoutMs = 5000;
|
||||
private const int MaxConcurrentChecks = 10;
|
||||
private readonly SemaphoreSlim _throttle = new(MaxConcurrentChecks, MaxConcurrentChecks);
|
||||
|
||||
public ConnectionStatusChecker(ConnectionTreeModel model)
|
||||
{
|
||||
_model = model;
|
||||
_timer = new Timer(CheckAllConnections, null, 0, CheckIntervalMs);
|
||||
}
|
||||
|
||||
public void UpdateModel(ConnectionTreeModel model)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
private void CheckAllConnections(object state)
|
||||
{
|
||||
if (!OptionsAppearancePage.Default.ShowStatusIndicatorInTree)
|
||||
return;
|
||||
|
||||
if (_model == null)
|
||||
return;
|
||||
|
||||
var connections = GetAllConnections(_model);
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
Task.Run(() => CheckConnectionStatusThrottled(connection));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckConnectionStatusThrottled(ConnectionInfo connection)
|
||||
{
|
||||
await _throttle.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
CheckConnectionStatus(connection);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_throttle.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckConnectionStatus(ConnectionInfo connection)
|
||||
{
|
||||
if (string.IsNullOrEmpty(connection.Hostname))
|
||||
{
|
||||
connection.HostStatus = HostStatus.Unknown;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var ping = new Ping();
|
||||
PingReply reply = ping.Send(connection.Hostname, PingTimeoutMs);
|
||||
connection.HostStatus = reply?.Status == IPStatus.Success
|
||||
? HostStatus.Online
|
||||
: HostStatus.Offline;
|
||||
}
|
||||
catch (PingException)
|
||||
{
|
||||
connection.HostStatus = HostStatus.Offline;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
connection.HostStatus = HostStatus.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ConnectionInfo> GetAllConnections(ConnectionTreeModel model)
|
||||
{
|
||||
var result = new List<ConnectionInfo>();
|
||||
foreach (var root in model.RootNodes)
|
||||
{
|
||||
CollectConnections(root, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void CollectConnections(ConnectionInfo node, List<ConnectionInfo> result)
|
||||
{
|
||||
if (node is ContainerInfo container)
|
||||
{
|
||||
foreach (var child in container.Children)
|
||||
{
|
||||
CollectConnections(child, result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
_throttle?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using mRemoteNG.Connection;
|
||||
using mRemoteNG.Container;
|
||||
using mRemoteNG.Properties;
|
||||
using mRemoteNG.Themes;
|
||||
using mRemoteNG.Tools;
|
||||
using mRemoteNG.Tools.Clipboard;
|
||||
using mRemoteNG.Tree;
|
||||
using mRemoteNG.Tree.ClickHandlers;
|
||||
@@ -29,6 +30,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
private readonly PuttySessionsManager _puttySessionsManager = PuttySessionsManager.Instance;
|
||||
private readonly StatusImageList _statusImageList = new();
|
||||
private ThemeManager _themeManager;
|
||||
private ConnectionStatusChecker _connectionStatusChecker;
|
||||
|
||||
private readonly ConnectionTreeSearchTextFilter _connectionTreeSearchTextFilter = new();
|
||||
|
||||
@@ -101,6 +103,7 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
{
|
||||
components?.Dispose();
|
||||
_statusImageList?.Dispose();
|
||||
_connectionStatusChecker?.Dispose();
|
||||
|
||||
_themeManager.ThemeChanged -= ThemeManagerOnThemeChanged;
|
||||
}
|
||||
@@ -202,6 +205,9 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
NodeSearcher = new NodeSearcher(newModel);
|
||||
ExecutePostSetupActions();
|
||||
AutoResizeColumn(Columns[0]);
|
||||
|
||||
_connectionStatusChecker?.Dispose();
|
||||
_connectionStatusChecker = new ConnectionStatusChecker(newModel);
|
||||
}
|
||||
|
||||
private void RegisterModelUpdateHandlers(ConnectionTreeModel newModel)
|
||||
@@ -234,7 +240,8 @@ namespace mRemoteNG.UI.Controls.ConnectionTree
|
||||
string property = propertyChangedEventArgs.PropertyName;
|
||||
if (property != nameof(ConnectionInfo.Name)
|
||||
&& property != nameof(ConnectionInfo.OpenConnections)
|
||||
&& property != nameof(ConnectionInfo.Icon))
|
||||
&& property != nameof(ConnectionInfo.Icon)
|
||||
&& property != nameof(ConnectionInfo.HostStatus))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
lblLanguage = new MrngLabel();
|
||||
chkShowFullConnectionsFilePathInTitle = new MrngCheckBox();
|
||||
chkShowDescriptionTooltipsInTree = new MrngCheckBox();
|
||||
chkShowStatusIndicatorInTree = new MrngCheckBox();
|
||||
chkShowSystemTrayIcon = new MrngCheckBox();
|
||||
chkMinimizeToSystemTray = new MrngCheckBox();
|
||||
chkCloseToSystemTray = new MrngCheckBox();
|
||||
@@ -76,18 +77,6 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
lblLanguage.TabIndex = 0;
|
||||
lblLanguage.Text = "Language";
|
||||
//
|
||||
// chkShowFullConnectionsFilePathInTitle
|
||||
//
|
||||
chkShowFullConnectionsFilePathInTitle._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkShowFullConnectionsFilePathInTitle.AutoSize = true;
|
||||
chkShowFullConnectionsFilePathInTitle.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkShowFullConnectionsFilePathInTitle.Location = new System.Drawing.Point(3, 130);
|
||||
chkShowFullConnectionsFilePathInTitle.Name = "chkShowFullConnectionsFilePathInTitle";
|
||||
chkShowFullConnectionsFilePathInTitle.Size = new System.Drawing.Size(268, 17);
|
||||
chkShowFullConnectionsFilePathInTitle.TabIndex = 4;
|
||||
chkShowFullConnectionsFilePathInTitle.Text = "Show full connections file path in window title";
|
||||
chkShowFullConnectionsFilePathInTitle.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// chkShowDescriptionTooltipsInTree
|
||||
//
|
||||
chkShowDescriptionTooltipsInTree._mice = MrngCheckBox.MouseState.OUT;
|
||||
@@ -100,12 +89,36 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
chkShowDescriptionTooltipsInTree.Text = "Show description tooltips in connection tree";
|
||||
chkShowDescriptionTooltipsInTree.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// chkShowStatusIndicatorInTree
|
||||
//
|
||||
chkShowStatusIndicatorInTree._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkShowStatusIndicatorInTree.AutoSize = true;
|
||||
chkShowStatusIndicatorInTree.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkShowStatusIndicatorInTree.Location = new System.Drawing.Point(3, 130);
|
||||
chkShowStatusIndicatorInTree.Name = "chkShowStatusIndicatorInTree";
|
||||
chkShowStatusIndicatorInTree.Size = new System.Drawing.Size(290, 17);
|
||||
chkShowStatusIndicatorInTree.TabIndex = 10;
|
||||
chkShowStatusIndicatorInTree.Text = "Show host status indicator in connection tree";
|
||||
chkShowStatusIndicatorInTree.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// chkShowFullConnectionsFilePathInTitle
|
||||
//
|
||||
chkShowFullConnectionsFilePathInTitle._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkShowFullConnectionsFilePathInTitle.AutoSize = true;
|
||||
chkShowFullConnectionsFilePathInTitle.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkShowFullConnectionsFilePathInTitle.Location = new System.Drawing.Point(3, 153);
|
||||
chkShowFullConnectionsFilePathInTitle.Name = "chkShowFullConnectionsFilePathInTitle";
|
||||
chkShowFullConnectionsFilePathInTitle.Size = new System.Drawing.Size(268, 17);
|
||||
chkShowFullConnectionsFilePathInTitle.TabIndex = 4;
|
||||
chkShowFullConnectionsFilePathInTitle.Text = "Show full connections file path in window title";
|
||||
chkShowFullConnectionsFilePathInTitle.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// chkShowSystemTrayIcon
|
||||
//
|
||||
chkShowSystemTrayIcon._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkShowSystemTrayIcon.AutoSize = true;
|
||||
chkShowSystemTrayIcon.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkShowSystemTrayIcon.Location = new System.Drawing.Point(3, 176);
|
||||
chkShowSystemTrayIcon.Location = new System.Drawing.Point(3, 199);
|
||||
chkShowSystemTrayIcon.Name = "chkShowSystemTrayIcon";
|
||||
chkShowSystemTrayIcon.Size = new System.Drawing.Size(178, 17);
|
||||
chkShowSystemTrayIcon.TabIndex = 5;
|
||||
@@ -117,7 +130,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
chkMinimizeToSystemTray._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkMinimizeToSystemTray.AutoSize = true;
|
||||
chkMinimizeToSystemTray.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkMinimizeToSystemTray.Location = new System.Drawing.Point(3, 199);
|
||||
chkMinimizeToSystemTray.Location = new System.Drawing.Point(3, 222);
|
||||
chkMinimizeToSystemTray.Name = "chkMinimizeToSystemTray";
|
||||
chkMinimizeToSystemTray.Size = new System.Drawing.Size(147, 17);
|
||||
chkMinimizeToSystemTray.TabIndex = 6;
|
||||
@@ -129,7 +142,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
chkCloseToSystemTray._mice = MrngCheckBox.MouseState.OUT;
|
||||
chkCloseToSystemTray.AutoSize = true;
|
||||
chkCloseToSystemTray.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
chkCloseToSystemTray.Location = new System.Drawing.Point(3, 222);
|
||||
chkCloseToSystemTray.Location = new System.Drawing.Point(3, 245);
|
||||
chkCloseToSystemTray.Name = "chkCloseToSystemTray";
|
||||
chkCloseToSystemTray.Size = new System.Drawing.Size(129, 17);
|
||||
chkCloseToSystemTray.TabIndex = 7;
|
||||
@@ -143,13 +156,14 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
pnlOptions.Controls.Add(chkMinimizeToSystemTray);
|
||||
pnlOptions.Controls.Add(lblLanguageRestartRequired);
|
||||
pnlOptions.Controls.Add(chkShowSystemTrayIcon);
|
||||
pnlOptions.Controls.Add(chkShowStatusIndicatorInTree);
|
||||
pnlOptions.Controls.Add(chkShowDescriptionTooltipsInTree);
|
||||
pnlOptions.Controls.Add(lblLanguage);
|
||||
pnlOptions.Controls.Add(chkShowFullConnectionsFilePathInTitle);
|
||||
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, 267);
|
||||
pnlOptions.Size = new System.Drawing.Size(610, 290);
|
||||
pnlOptions.TabIndex = 8;
|
||||
//
|
||||
// lblRegistrySettingsUsedInfo
|
||||
@@ -183,6 +197,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
internal Controls.MrngLabel lblLanguage;
|
||||
internal MrngCheckBox chkShowFullConnectionsFilePathInTitle;
|
||||
internal MrngCheckBox chkShowDescriptionTooltipsInTree;
|
||||
internal MrngCheckBox chkShowStatusIndicatorInTree;
|
||||
internal MrngCheckBox chkShowSystemTrayIcon;
|
||||
internal MrngCheckBox chkMinimizeToSystemTray;
|
||||
internal MrngCheckBox chkCloseToSystemTray;
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
lblLanguageRestartRequired.Text =
|
||||
string.Format(Language.LanguageRestartRequired, Application.ProductName);
|
||||
chkShowDescriptionTooltipsInTree.Text = Language.ShowDescriptionTooltips;
|
||||
chkShowStatusIndicatorInTree.Text = Language.ShowStatusIndicatorInTree;
|
||||
chkShowFullConnectionsFilePathInTitle.Text = Language.ShowFullConsFilePath;
|
||||
chkShowSystemTrayIcon.Text = Language.AlwaysShowSysTrayIcon;
|
||||
chkMinimizeToSystemTray.Text = Language.MinimizeToSysTray;
|
||||
@@ -63,6 +64,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
}
|
||||
|
||||
chkShowDescriptionTooltipsInTree.Checked = Properties.OptionsAppearancePage.Default.ShowDescriptionTooltipsInTree;
|
||||
chkShowStatusIndicatorInTree.Checked = Properties.OptionsAppearancePage.Default.ShowStatusIndicatorInTree;
|
||||
chkShowFullConnectionsFilePathInTitle.Checked = Properties.OptionsAppearancePage.Default.ShowCompleteConsPathInTitle;
|
||||
chkShowSystemTrayIcon.Checked = Properties.OptionsAppearancePage.Default.ShowSystemTrayIcon;
|
||||
chkMinimizeToSystemTray.Checked = Properties.OptionsAppearancePage.Default.MinimizeToTray;
|
||||
@@ -82,6 +84,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
|
||||
}
|
||||
|
||||
Properties.OptionsAppearancePage.Default.ShowDescriptionTooltipsInTree = chkShowDescriptionTooltipsInTree.Checked;
|
||||
Properties.OptionsAppearancePage.Default.ShowStatusIndicatorInTree = chkShowStatusIndicatorInTree.Checked;
|
||||
Properties.OptionsAppearancePage.Default.ShowCompleteConsPathInTitle = chkShowFullConnectionsFilePathInTitle.Checked;
|
||||
FrmMain.Default.ShowFullPathInTitle = chkShowFullConnectionsFilePathInTitle.Checked;
|
||||
|
||||
|
||||
12
mRemoteNG/UI/Forms/frmMain.Designer.cs
generated
12
mRemoteNG/UI/Forms/frmMain.Designer.cs
generated
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
161
mRemoteNG/UI/Menu/msMain/SessionsMenu.cs
Normal file
161
mRemoteNG/UI/Menu/msMain/SessionsMenu.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,11 @@ namespace mRemoteNG.UI
|
||||
return $"Connection_{icon}_{status}";
|
||||
}
|
||||
|
||||
private static string BuildStatusIconName(string baseKey, HostStatus hostStatus)
|
||||
{
|
||||
return $"{baseKey}_{hostStatus}";
|
||||
}
|
||||
|
||||
private const string DefaultConnectionIcon = "";
|
||||
|
||||
private string GetConnectionIcon(ConnectionInfo connection)
|
||||
@@ -67,19 +72,80 @@ namespace mRemoteNG.UI
|
||||
}
|
||||
|
||||
bool connected = connection.OpenConnections.Count > 0;
|
||||
string name = BuildConnectionIconName(connection.Icon, connected);
|
||||
string baseKey = BuildConnectionIconName(connection.Icon, connected);
|
||||
|
||||
bool showStatusIndicator = Properties.OptionsAppearancePage.Default.ShowStatusIndicatorInTree;
|
||||
string name = showStatusIndicator
|
||||
? BuildStatusIconName(baseKey, connection.HostStatus)
|
||||
: baseKey;
|
||||
|
||||
if (ImageList.Images.ContainsKey(name)) return name;
|
||||
|
||||
Icon image = ConnectionIcon.FromString(connection.Icon);
|
||||
if (image == null)
|
||||
{
|
||||
return DefaultConnectionIcon;
|
||||
}
|
||||
|
||||
ImageList.Images.Add(BuildConnectionIconName(connection.Icon, false), image);
|
||||
ImageList.Images.Add(BuildConnectionIconName(connection.Icon, true), Overlay(image, Properties.Resources.ConnectedOverlay));
|
||||
Bitmap defaultBitmap = image.ToBitmap();
|
||||
Bitmap playBitmap = Overlay(image, Properties.Resources.ConnectedOverlay);
|
||||
|
||||
if (showStatusIndicator)
|
||||
{
|
||||
foreach (HostStatus status in Enum.GetValues<HostStatus>())
|
||||
{
|
||||
Color barColor = GetStatusColor(status);
|
||||
string defaultStatusKey = BuildStatusIconName(BuildConnectionIconName(connection.Icon, false), status);
|
||||
string playStatusKey = BuildStatusIconName(BuildConnectionIconName(connection.Icon, true), status);
|
||||
|
||||
if (!ImageList.Images.ContainsKey(defaultStatusKey))
|
||||
ImageList.Images.Add(defaultStatusKey, AddStatusBar(defaultBitmap, barColor));
|
||||
if (!ImageList.Images.ContainsKey(playStatusKey))
|
||||
ImageList.Images.Add(playStatusKey, AddStatusBar(playBitmap, barColor));
|
||||
}
|
||||
|
||||
defaultBitmap.Dispose();
|
||||
playBitmap.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ImageList.Images.ContainsKey(BuildConnectionIconName(connection.Icon, false)))
|
||||
ImageList.Images.Add(BuildConnectionIconName(connection.Icon, false), defaultBitmap);
|
||||
else
|
||||
defaultBitmap.Dispose();
|
||||
|
||||
if (!ImageList.Images.ContainsKey(BuildConnectionIconName(connection.Icon, true)))
|
||||
ImageList.Images.Add(BuildConnectionIconName(connection.Icon, true), playBitmap);
|
||||
else
|
||||
playBitmap.Dispose();
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static Color GetStatusColor(HostStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
HostStatus.Online => Color.FromArgb(0, 180, 0),
|
||||
HostStatus.Offline => Color.FromArgb(200, 0, 0),
|
||||
_ => Color.FromArgb(160, 160, 160)
|
||||
};
|
||||
}
|
||||
|
||||
private static Bitmap AddStatusBar(Bitmap source, Color barColor)
|
||||
{
|
||||
Bitmap result = new(source.Width, source.Height);
|
||||
using (Graphics gr = Graphics.FromImage(result))
|
||||
{
|
||||
gr.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height));
|
||||
using SolidBrush brush = new(barColor);
|
||||
gr.FillRectangle(brush, 0, 0, 3, source.Height);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Bitmap Overlay(Icon background, Image foreground)
|
||||
{
|
||||
Bitmap result = new(background.ToBitmap(), new Size(16, 16));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user