mirror of https://github.com/stascorp/rdpwrap
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
5.6 KiB
148 lines
5.6 KiB
// Copyright 2026 sjackson0109 — Apache License 2.0
|
|
//
|
|
// COM interop helpers for hosting the MsRdpClient2 ActiveX control and
|
|
// receiving its disconnection event without requiring pre-generated TLB wrappers.
|
|
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace RDPCheck;
|
|
|
|
// ── COM interfaces needed for connection-point event subscription ────────────
|
|
|
|
[ComImport]
|
|
[Guid("B196B284-BAB4-101A-B69C-00AA00341D07")]
|
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
internal interface IConnectionPointContainer
|
|
{
|
|
void EnumConnectionPoints(out IntPtr ppEnum);
|
|
void FindConnectionPoint(ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IConnectionPoint ppCP);
|
|
}
|
|
|
|
[ComImport]
|
|
[Guid("B196B287-BAB4-101A-B69C-00AA00341D07")]
|
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
internal interface IConnectionPoint
|
|
{
|
|
void GetConnectionInterface(out Guid pIID);
|
|
void GetConnectionPointContainer([MarshalAs(UnmanagedType.Interface)] out IConnectionPointContainer ppCPC);
|
|
void Advise([MarshalAs(UnmanagedType.Interface)] object pUnkSink, out uint pdwCookie);
|
|
void Unadvise(uint dwCookie);
|
|
void EnumConnections(out IntPtr ppEnum);
|
|
}
|
|
|
|
// ── IDispatch-based event interface (dispinterface) ──────────────────────────
|
|
// Matching the DIID of IMsTscAxEvents so the RDP control can route events here.
|
|
|
|
[ComVisible(true)]
|
|
[Guid("336D5562-EBA6-11D0-B0B0-00C04FD610D0")]
|
|
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
|
|
internal interface IRdpEvents
|
|
{
|
|
[DispId(1)] void OnConnecting();
|
|
[DispId(2)] void OnConnected();
|
|
[DispId(3)] void OnLoginComplete();
|
|
[DispId(4)] void OnDisconnected(int discReason);
|
|
// Additional high-DISPID events are ignored (not listed here)
|
|
}
|
|
|
|
// ── Managed event sink ────────────────────────────────────────────────────────
|
|
|
|
[ComVisible(true)]
|
|
[ClassInterface(ClassInterfaceType.None)]
|
|
internal sealed class RdpEventSink : IRdpEvents
|
|
{
|
|
private readonly Action<int> _onDisconnected;
|
|
|
|
public RdpEventSink(Action<int> onDisconnected) => _onDisconnected = onDisconnected;
|
|
|
|
public void OnConnecting() { }
|
|
public void OnConnected() { }
|
|
public void OnLoginComplete() { }
|
|
public void OnDisconnected(int discReason) => _onDisconnected(discReason);
|
|
}
|
|
|
|
// ── AxHost wrapper for MsRdpClient2 ──────────────────────────────────────────
|
|
|
|
internal sealed class AxRdpClient2 : AxHost
|
|
{
|
|
// CLSID for MsRdpClient2 (msrdp.ocx / mstscax.dll)
|
|
private static readonly Guid Clsid = new("9059F30F-4EB1-4BD2-9FDC-36F43A218F4A");
|
|
// DIID for the default source interface (IMsTscAxEvents / DIMsTscAxEvents)
|
|
private static readonly Guid DiidEvents = new("336D5562-EBA6-11D0-B0B0-00C04FD610D0");
|
|
|
|
private dynamic? _ocx;
|
|
private IConnectionPoint? _cp;
|
|
private uint _cookie;
|
|
private RdpEventSink? _sink;
|
|
|
|
/// <summary>Raised on the UI thread when the RDP control disconnects.</summary>
|
|
public event EventHandler<int>? Disconnected;
|
|
|
|
public AxRdpClient2() : base(Clsid.ToString("B")) { }
|
|
|
|
protected override void AttachInterfaces()
|
|
{
|
|
_ocx = GetOcx() as dynamic;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wires up the COM event sink. Call once after the handle has been created
|
|
/// (i.e. after the form is shown).
|
|
/// </summary>
|
|
public void SubscribeEvents()
|
|
{
|
|
if (_ocx is null) return;
|
|
try
|
|
{
|
|
var cpContainer = (IConnectionPointContainer)_ocx;
|
|
var eventsIid = DiidEvents;
|
|
cpContainer.FindConnectionPoint(ref eventsIid, out var cp);
|
|
_sink = new RdpEventSink(reason => Invoke(
|
|
(Action)(() => Disconnected?.Invoke(this, reason))));
|
|
cp.Advise(_sink, out _cookie);
|
|
_cp = cp;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine($"[RDP] SubscribeEvents failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>Unsubscribes the event sink.</summary>
|
|
public void UnsubscribeEvents()
|
|
{
|
|
try { _cp?.Unadvise(_cookie); } catch { }
|
|
_cp = null;
|
|
}
|
|
|
|
// ── Typed property / method wrappers ────────────────────────────────────
|
|
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string Server { set { if (_ocx != null) _ocx.Server = value; } }
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string UserName { set { if (_ocx != null) _ocx.UserName = value; } }
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string DisconnectedText { set { if (_ocx != null) _ocx.DisconnectedText = value; } }
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string ConnectingText { set { if (_ocx != null) _ocx.ConnectingText = value; } }
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string ConnectedStatusText { set { if (_ocx != null) _ocx.ConnectedStatusText = value; } }
|
|
|
|
public void SetPort(int port)
|
|
{
|
|
try { if (_ocx != null) _ocx.AdvancedSettings2.RDPPort = port; } catch { }
|
|
}
|
|
|
|
public void Connect()
|
|
{
|
|
try { _ocx?.Connect(); } catch { }
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing) UnsubscribeEvents();
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|