mirror of
https://github.com/mRemoteNG/mRemoteNG.git
synced 2026-02-25 19:38:37 +08:00
Merge pull request #2957 from mRemoteNG/copilot/add-anydesk-connection-plugin
Add native AnyDesk protocol support to mRemoteNG
This commit is contained in:
@@ -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/).
|
||||
|
||||
|
||||
411
mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Normal file
411
mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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 == "")
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
125
mRemoteNGDocumentation/protocols/anydesk.rst
Normal file
125
mRemoteNGDocumentation/protocols/anydesk.rst
Normal 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/>`_
|
||||
Reference in New Issue
Block a user