mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-26 03:58:45 +08:00
Compare commits
48 Commits
copilot/ad
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b59518364 | ||
|
|
ba72c1666c | ||
|
|
43735b1d04 | ||
|
|
002f6cb290 | ||
|
|
92c617d442 | ||
|
|
764b96f864 | ||
|
|
e30a8ad3f0 | ||
|
|
1e85969e3a | ||
|
|
1a47bba982 | ||
|
|
1cc5f05bf5 | ||
|
|
7e0277f85d | ||
|
|
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\/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");
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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