Merge pull request #2957 from mRemoteNG/copilot/add-anydesk-connection-plugin

Add native AnyDesk protocol support to mRemoteNG
This commit is contained in:
Dimitrij
2025-10-20 22:35:51 +01:00
committed by GitHub
7 changed files with 547 additions and 0 deletions

View File

@@ -72,6 +72,7 @@ The following protocols are supported:
* rlogin (Remote Login)
* Raw Socket Connections
* Powershell remoting
* AnyDesk
For a detailed feature list and general usage support, refer to the [Documentation](https://mremoteng.readthedocs.io/en/latest/).

View File

@@ -0,0 +1,411 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.App;
using mRemoteNG.Messages;
using mRemoteNG.Resources.Language;
namespace mRemoteNG.Connection.Protocol.AnyDesk
{
[SupportedOSPlatform("windows")]
public class ProtocolAnyDesk : ProtocolBase
{
#region Private Fields
private IntPtr _handle;
private readonly ConnectionInfo _connectionInfo;
private Process _process;
private const string DefaultAnydeskPath = @"C:\Program Files (x86)\AnyDesk\AnyDesk.exe";
private const string AlternateAnydeskPath = @"C:\Program Files\AnyDesk\AnyDesk.exe";
#endregion
#region Constructor
public ProtocolAnyDesk(ConnectionInfo connectionInfo)
{
_connectionInfo = connectionInfo;
}
#endregion
#region Public Methods
public override bool Initialize()
{
return base.Initialize();
}
public override bool Connect()
{
try
{
Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg,
"Attempting to start AnyDesk connection.", true);
// Validate AnyDesk installation
string anydeskPath = FindAnydeskExecutable();
if (string.IsNullOrEmpty(anydeskPath))
{
Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
"AnyDesk is not installed. Please install AnyDesk to use this protocol.", true);
return false;
}
// Validate connection info
if (string.IsNullOrEmpty(_connectionInfo.Hostname))
{
Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
"AnyDesk ID is required in the Hostname field.", true);
return false;
}
// Start AnyDesk connection
if (!StartAnydeskConnection(anydeskPath))
{
return false;
}
return true;
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage(Language.ConnectionFailed, ex);
return false;
}
}
public override void Focus()
{
try
{
if (_handle != IntPtr.Zero)
{
NativeMethods.SetForegroundWindow(_handle);
}
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage(Language.IntAppFocusFailed, ex);
}
}
protected override void Resize(object sender, EventArgs e)
{
try
{
if (_handle == IntPtr.Zero || InterfaceControl.Size == Size.Empty)
return;
// Use ClientRectangle to account for padding (for connection frame color)
Rectangle clientRect = InterfaceControl.ClientRectangle;
NativeMethods.MoveWindow(_handle,
clientRect.X - SystemInformation.FrameBorderSize.Width,
clientRect.Y - (SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height),
clientRect.Width + SystemInformation.FrameBorderSize.Width * 2,
clientRect.Height + SystemInformation.CaptionHeight +
SystemInformation.FrameBorderSize.Height * 2, true);
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage(Language.IntAppResizeFailed, ex);
}
}
public override void Close()
{
try
{
// Try to close all AnyDesk processes related to this connection
if (_process != null)
{
try
{
if (!_process.HasExited)
{
_process.Kill();
}
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage(Language.IntAppKillFailed, ex);
}
finally
{
_process?.Dispose();
_process = null;
}
}
// Also try to close by window handle if we have it
if (_handle != IntPtr.Zero)
{
try
{
NativeMethods.SendMessage(_handle, 0x0010, IntPtr.Zero, IntPtr.Zero); // WM_CLOSE
}
catch
{
// Ignore errors when closing by handle
}
_handle = IntPtr.Zero;
}
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage("Error closing AnyDesk connection.", ex);
}
base.Close();
}
#endregion
#region Private Methods
private string FindAnydeskExecutable()
{
// Check common installation paths
if (File.Exists(DefaultAnydeskPath))
{
return DefaultAnydeskPath;
}
if (File.Exists(AlternateAnydeskPath))
{
return AlternateAnydeskPath;
}
// Check if it's in PATH
string pathVariable = Environment.GetEnvironmentVariable("PATH");
if (pathVariable != null)
{
var paths = pathVariable.Split(Path.PathSeparator);
foreach (var path in paths)
{
var exePath = Path.Combine(path.Trim(), "AnyDesk.exe");
if (File.Exists(exePath))
{
return exePath;
}
}
}
return null;
}
private bool StartAnydeskConnection(string anydeskPath)
{
try
{
// Build AnyDesk arguments
// Format: AnyDesk.exe [ID|alias@ad] [options]
// Hostname field contains the AnyDesk ID (e.g., 123456789 or alias@ad)
// Username field is optional and not used in the CLI (reserved for future use)
// Password field is piped via stdin when --with-password flag is used
string anydeskId = _connectionInfo.Hostname.Trim();
string arguments = $"{anydeskId}";
// Add --with-password flag if password is provided
bool hasPassword = !string.IsNullOrEmpty(_connectionInfo.Password);
if (hasPassword)
{
arguments += " --with-password";
}
// Add --plain flag to minimize UI (optional)
arguments += " --plain";
Runtime.MessageCollector?.AddMessage(MessageClass.InformationMsg,
$"Starting AnyDesk with ID: {anydeskId}", true);
// If password is provided, we need to pipe it to AnyDesk
if (hasPassword)
{
return StartAnydeskWithPassword(anydeskPath, arguments);
}
else
{
return StartAnydeskWithoutPassword(anydeskPath, arguments);
}
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage("Failed to start AnyDesk connection.", ex);
return false;
}
}
private bool StartAnydeskWithPassword(string anydeskPath, string arguments)
{
try
{
// Use PowerShell to pipe the password to AnyDesk
// This is the recommended way according to AnyDesk documentation
string escapedPassword = _connectionInfo.Password.Replace("'", "''");
string powershellCommand = $"echo '{escapedPassword}' | & '{anydeskPath}' {arguments}";
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-WindowStyle Hidden -Command \"{powershellCommand}\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = false,
RedirectStandardError = false,
RedirectStandardInput = false
},
EnableRaisingEvents = true
};
_process.Exited += ProcessExited;
_process.Start();
// Wait for the AnyDesk window to appear
// Note: The window belongs to the AnyDesk process, not PowerShell
if (!WaitForAnydeskWindow())
{
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
"AnyDesk window did not appear within the expected time.", true);
return false;
}
base.Connect();
return true;
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage("Failed to start AnyDesk with password.", ex);
return false;
}
}
private bool StartAnydeskWithoutPassword(string anydeskPath, string arguments)
{
try
{
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = anydeskPath,
Arguments = arguments,
UseShellExecute = true
},
EnableRaisingEvents = true
};
_process.Exited += ProcessExited;
_process.Start();
// Wait for the AnyDesk window to appear
if (!WaitForAnydeskWindow())
{
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
"AnyDesk window did not appear within the expected time.", true);
return false;
}
base.Connect();
return true;
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionMessage("Failed to start AnyDesk connection.", ex);
return false;
}
}
private bool WaitForAnydeskWindow()
{
// Wait up to 10 seconds for AnyDesk window to appear
int maxWaitTime = 10000; // 10 seconds
int waitInterval = 100; // 100 ms
int elapsedTime = 0;
while (elapsedTime < maxWaitTime)
{
// Find AnyDesk process by name
Process[] anydeskProcesses = Process.GetProcessesByName("AnyDesk");
Process processToKeep = null;
try
{
foreach (Process anydeskProcess in anydeskProcesses)
{
try
{
anydeskProcess.Refresh();
// Try to get the main window handle
if (anydeskProcess.MainWindowHandle != IntPtr.Zero)
{
_handle = anydeskProcess.MainWindowHandle;
// Store the actual AnyDesk process for later cleanup
// Dispose the PowerShell process if it's different
if (_process != null && _process.ProcessName != "AnyDesk")
{
_process.Exited -= ProcessExited;
_process = anydeskProcess;
_process.EnableRaisingEvents = true;
_process.Exited += ProcessExited;
processToKeep = anydeskProcess;
}
// Try to integrate the window
if (InterfaceControl != null)
{
NativeMethods.SetParent(_handle, InterfaceControl.Handle);
Resize(this, new EventArgs());
}
return true;
}
}
catch
{
// Ignore errors for individual processes
}
}
}
finally
{
foreach (Process anydeskProcess in anydeskProcesses)
{
if (anydeskProcess != processToKeep)
{
anydeskProcess.Dispose();
}
}
}
Thread.Sleep(waitInterval);
elapsedTime += waitInterval;
}
return false;
}
private void ProcessExited(object sender, EventArgs e)
{
Event_Closed(this);
}
#endregion
#region Enumerations
public enum Defaults
{
Port = 0 // AnyDesk doesn't use a traditional port from the client side
}
#endregion
}
}

View File

@@ -10,6 +10,7 @@ using System;
using mRemoteNG.Connection.Protocol.PowerShell;
using mRemoteNG.Connection.Protocol.WSL;
using mRemoteNG.Connection.Protocol.Terminal;
using mRemoteNG.Connection.Protocol.AnyDesk;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
@@ -53,6 +54,8 @@ namespace mRemoteNG.Connection.Protocol
return new ProtocolWSL(connectionInfo);
case ProtocolType.Terminal:
return new ProtocolTerminal(connectionInfo);
case ProtocolType.AnyDesk:
return new ProtocolAnyDesk(connectionInfo);
case ProtocolType.IntApp:
if (connectionInfo.ExtApp == "")
{

View File

@@ -43,6 +43,9 @@ namespace mRemoteNG.Connection.Protocol
[LocalizedAttributes.LocalizedDescription(nameof(Language.Terminal))]
Terminal = 12,
[LocalizedAttributes.LocalizedDescription(nameof(Language.AnyDesk))]
AnyDesk = 13,
[LocalizedAttributes.LocalizedDescription(nameof(Language.ExternalTool))]
IntApp = 20
}

View File

@@ -159,6 +159,9 @@
<data name="AskUpdatesMainInstruction" xml:space="preserve">
<value>Automatic update settings</value>
</data>
<data name="AnyDesk" xml:space="preserve">
<value>AnyDesk</value>
</data>
<data name="Ard" xml:space="preserve">
<value>ARD (Apple Remote Desktop)</value>
</data>

View File

@@ -7,5 +7,6 @@ mRemoteNG supports several remote connection protocols. See each page for more d
.. toctree::
:maxdepth: 1
protocols/anydesk.rst
protocols/rdp.rst

View File

@@ -0,0 +1,125 @@
****************
AnyDesk Protocol
****************
AnyDesk is a remote desktop application that provides a fast and secure connection to remote computers. mRemoteNG supports connecting to AnyDesk through its command-line interface.
Prerequisites
=============
Before using the AnyDesk protocol in mRemoteNG, ensure that:
1. AnyDesk is installed on your local machine
2. The AnyDesk executable is located in one of the default installation paths:
- ``C:\Program Files (x86)\AnyDesk\AnyDesk.exe``
- ``C:\Program Files\AnyDesk\AnyDesk.exe``
- Or in your system's PATH environment variable
Configuration
=============
To configure an AnyDesk connection in mRemoteNG:
Connection Properties
---------------------
.. list-table::
:widths: 30 70
:header-rows: 1
* - Property
- Description
* - Protocol
- Select ``AnyDesk`` from the protocol dropdown
* - Hostname
- The AnyDesk ID or alias of the remote computer (e.g., ``123456789`` or ``mycomputer@ad``)
* - Username
- Reserved for future use (currently not used by AnyDesk CLI)
* - Password
- The password for unattended access (optional). If provided, it will be automatically passed to AnyDesk using the ``--with-password`` flag
Usage Examples
==============
Basic Connection
----------------
For a simple connection without password:
- **Protocol**: AnyDesk
- **Hostname**: 123456789
This will launch AnyDesk and connect to the specified ID. You will be prompted to enter the password manually if required.
Unattended Access
-----------------
For automatic connection with password:
- **Protocol**: AnyDesk
- **Hostname**: 123456789
- **Password**: your_anydesk_password
This will automatically pipe the password to AnyDesk for unattended access.
Using Alias
-----------
If you have configured an alias in AnyDesk:
- **Protocol**: AnyDesk
- **Hostname**: mycomputer@ad
- **Password**: your_anydesk_password
Features
========
- **Automatic Password Authentication**: When a password is provided, mRemoteNG uses PowerShell to pipe the password to AnyDesk as recommended by AnyDesk's CLI documentation
- **Window Integration**: The AnyDesk window is embedded within mRemoteNG's interface for a seamless experience
- **Plain Mode**: AnyDesk is launched with the ``--plain`` flag to minimize the AnyDesk UI and provide a cleaner connection experience
Troubleshooting
===============
AnyDesk Not Found
-----------------
If you receive an error that AnyDesk is not installed:
1. Verify that AnyDesk is installed on your machine
2. Check that the executable exists in one of the default paths
3. Alternatively, add the AnyDesk installation directory to your system's PATH environment variable
Connection Issues
-----------------
If the connection fails or the window doesn't appear:
1. Verify that the AnyDesk ID is correct
2. Check that the remote computer has AnyDesk running
3. Ensure the password is correct (for unattended access)
4. Check the message log in mRemoteNG for specific error messages
Window Not Embedding
--------------------
If the AnyDesk window doesn't appear embedded in mRemoteNG:
1. AnyDesk may take a few seconds to launch - wait up to 10 seconds
2. Some AnyDesk versions may not support window embedding
3. Check the Windows Task Manager to verify that AnyDesk.exe is running
Notes
=====
- The AnyDesk protocol works with the free version of AnyDesk
- For security reasons, passwords are never stored in plain text - they are encrypted by mRemoteNG
- The Username field is reserved for future use and is currently not utilized by the AnyDesk CLI
- AnyDesk connections are integrated into mRemoteNG's tabbed interface for easy management
References
==========
- `AnyDesk Command Line Interface Documentation <https://support.anydesk.com/Command_Line_Interface>`_
- `AnyDesk Official Website <https://anydesk.com/>`_