diff --git a/README.md b/README.md
index 59f8acbec..8dbd037db 100644
--- a/README.md
+++ b/README.md
@@ -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/).
diff --git a/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
new file mode 100644
index 000000000..8a7a00a66
--- /dev/null
+++ b/mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
@@ -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
+ }
+}
diff --git a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs
index 110a4b5db..3fddaa8a4 100644
--- a/mRemoteNG/Connection/Protocol/ProtocolFactory.cs
+++ b/mRemoteNG/Connection/Protocol/ProtocolFactory.cs
@@ -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 == "")
{
diff --git a/mRemoteNG/Connection/Protocol/ProtocolType.cs b/mRemoteNG/Connection/Protocol/ProtocolType.cs
index 20af662bd..d7a607e91 100644
--- a/mRemoteNG/Connection/Protocol/ProtocolType.cs
+++ b/mRemoteNG/Connection/Protocol/ProtocolType.cs
@@ -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
}
diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx
index 83b9c5c7f..ee3317fee 100644
--- a/mRemoteNG/Language/Language.resx
+++ b/mRemoteNG/Language/Language.resx
@@ -159,6 +159,9 @@
Automatic update settings
+
+ AnyDesk
+
ARD (Apple Remote Desktop)
diff --git a/mRemoteNGDocumentation/protocols.rst b/mRemoteNGDocumentation/protocols.rst
index 36f9c8d5d..454001d80 100644
--- a/mRemoteNGDocumentation/protocols.rst
+++ b/mRemoteNGDocumentation/protocols.rst
@@ -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
\ No newline at end of file
diff --git a/mRemoteNGDocumentation/protocols/anydesk.rst b/mRemoteNGDocumentation/protocols/anydesk.rst
new file mode 100644
index 000000000..75feb2479
--- /dev/null
+++ b/mRemoteNGDocumentation/protocols/anydesk.rst
@@ -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 `_
+- `AnyDesk Official Website `_