diff --git a/Cargo.lock b/Cargo.lock index 42676ca6..f7eebf6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1134,10 +1134,12 @@ dependencies = [ "diplomat-runtime", "embed-resource", "ironrdp", + "ironrdp-cliprdr-native", "sspi", "thiserror", "tracing", "tracing-subscriber", + "windows 0.48.0", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 5277dd46..7a42f284 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -19,7 +19,8 @@ doctest = false [dependencies] diplomat = "0.7.0" diplomat-runtime = "0.7.0" -ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd","graphics","input"] } +ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd","graphics","input","cliprdr"] } +ironrdp-cliprdr-native = { workspace = true } sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true tracing.workspace = true @@ -27,3 +28,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [target.'cfg(windows)'.build-dependencies] embed-resource = "2.2.0" + +[target.'cfg(windows)'.dependencies] +windows = { version = "0.48", features = [ + "Win32_Foundation", +] } \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.csproj b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.csproj index d358f1e4..5d0e34f1 100644 --- a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.csproj +++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.csproj @@ -9,13 +9,22 @@ true + - + - + + + + + + + \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs index cc0f207a..0a25d07b 100644 --- a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs +++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs @@ -7,25 +7,32 @@ using Avalonia.Threading; using System; using System.Diagnostics; using System.Net.Security; +using System.Runtime.InteropServices; using System.Threading.Tasks; +using Avalonia.Markup.Xaml; namespace Devolutions.IronRdp.AvaloniaExample; public partial class MainWindow : Window { + WriteableBitmap? _bitmap; + Canvas? _canvas; + Image? _image; + readonly InputDatabase? _inputDatabase = InputDatabase.New(); + ActiveStage? _activeStage; + DecodedImage? _decodedImage; + Framed? _framed; + WinCliprdr? _cliprdr; - WriteableBitmap? bitmap; - Canvas? canvas; - Image? image; - InputDatabase? inputDatabase = InputDatabase.New(); - ActiveStage? activeStage; - DecodedImage? decodedImage; - Framed? framed; public MainWindow() { InitializeComponent(); this.Opened += OnOpened; + } + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); } private void OnOpened(object? sender, EventArgs e) @@ -41,54 +48,84 @@ public partial class MainWindow : Window if (username == null || password == null || domain == null || server == null) { - Trace.TraceError("Please set the IRONRDP_USERNAME, IRONRDP_PASSWORD, IRONRDP_DOMAIN, and IRONRDP_SERVER environment variables"); + Trace.TraceError( + "Please set the IRONRDP_USERNAME, IRONRDP_PASSWORD, IRONRDP_DOMAIN, and IRONRDP_SERVER environment variables"); Close(); return; } - var width = 1280; - var height = 800; + const int width = 1280; + const int height = 800; - var config = buildConfig(username, password, domain, width, height); + var config = BuildConfig(username, password, domain, width, height); - var task = Connection.Connect(config, server); - bitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), Avalonia.Platform.PixelFormat.Rgba8888, AlphaFormat.Opaque); - canvas = this.FindControl("MainCanvas")!; - canvas.Focusable = true; - image = new Image { Width = width, Height = height, Source = this.bitmap }; - canvas.Children.Add(image); + CliprdrBackendFactory? factory = null; + var handle = GetWindowHandle(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && handle != null) + { + _cliprdr = WinCliprdr.New(handle.Value); + if (_cliprdr != null) + { + factory = _cliprdr.BackendFactory(); + } + } - canvas.KeyDown += Canvas_KeyDown; - canvas.KeyUp += Canvas_KeyUp; + + var task = Connection.Connect(config, server, factory); + + PostConnectSetup(width, height); task.ContinueWith(t => { if (t.IsFaulted) { - Exception e = t.Exception!; - Trace.TraceError("Error connecting to server: " + e.Message); + Exception connectError = t.Exception!; + Trace.TraceError("Error connecting to server: " + connectError.Message); Close(); return; } + var (res, framed) = t.Result; - this.decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), res.GetDesktopSize().GetHeight()); - this.activeStage = ActiveStage.New(res); - this.framed = framed; + this._decodedImage = DecodedImage.New(PixelFormat.RgbA32, res.GetDesktopSize().GetWidth(), + res.GetDesktopSize().GetHeight()); + this._activeStage = ActiveStage.New(res); + this._framed = framed; ReadPduAndProcessActiveStage(); + HandleClipboardEvents(); + }).ContinueWith(t => + { + if (t.IsFaulted) + { + Trace.TraceError("Error processing active stage: " + t.Exception!.Message); + Close(); + } + return; }); } + private void PostConnectSetup(int width, int height) + { + _bitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), + Avalonia.Platform.PixelFormat.Rgba8888, AlphaFormat.Opaque); + _canvas = this.FindControl("MainCanvas")!; + _canvas.Focusable = true; + _image = new Image { Width = width, Height = height, Source = this._bitmap }; + _canvas.Children.Add(_image); + _canvas.KeyDown += Canvas_KeyDown; + _canvas.KeyUp += Canvas_KeyUp; + } + private async void WriteDecodedImageToCanvas() { await Dispatcher.UIThread.InvokeAsync(() => { - var data = decodedImage!.GetData(); + var data = _decodedImage!.GetData(); var bufferSize = (int)data.GetSize(); var buffer = new byte[bufferSize]; data.Fill(buffer); - using (var bitmap = this.bitmap!.Lock()) + using (var bitmap = this._bitmap!.Lock()) { unsafe { @@ -98,13 +135,10 @@ public partial class MainWindow : Window } } - image!.InvalidateVisual(); + _image!.InvalidateVisual(); }); } - - - private void ReadPduAndProcessActiveStage() { Task.Run(async () => @@ -112,16 +146,61 @@ public partial class MainWindow : Window var keepLooping = true; while (keepLooping) { - var readPduTask = await framed!.ReadPdu(); + var readPduTask = await _framed!.ReadPdu(); Action action = readPduTask.Item1; byte[] payload = readPduTask.Item2; - var outputIterator = activeStage!.Process(decodedImage!, action, payload); + var outputIterator = _activeStage!.Process(_decodedImage!, action, payload); keepLooping = await HandleActiveStageOutput(outputIterator); } }); } - private static Config buildConfig(string username, string password, string domain, int width, int height) + private void HandleClipboardEvents() + { + Task.Run(async () => + { + while (true) + { + if (_cliprdr == null) + { + continue; + } + + var message = _cliprdr.NextClipboardMessageBlocking(); + + VecU8 frame; + var messageType = message.GetMessageType(); + Trace.TraceInformation("Clipboard message type: " + messageType); + if (messageType == ClipboardMessageType.SendFormatData) + { + var formatData = message.GetSendFormatData()!; + frame = _activeStage!.SubmitClipboardFormatData(formatData); + } + else if (messageType == ClipboardMessageType.SendInitiateCopy) + { + var initiateCopy = message.GetSendInitiateCopy()!; + frame = _activeStage!.InitiateClipboardCopy(initiateCopy); + } + else if (messageType == ClipboardMessageType.SendInitiatePaste) + { + var initiatePaste = message.GetSendInitiatePaste()!; + frame = _activeStage!.InitiateClipboardPaste(initiatePaste); + } + else + { + Console.WriteLine("Error in clipboard"); + break; + } + + var toWriteBack = new byte[frame.GetSize()]; + frame.Fill(toWriteBack); + + await _framed!.Write(toWriteBack); + } + }); + } + + private static Config BuildConfig(string username, string password, string domain, int width, int height) { ConfigBuilder configBuilder = ConfigBuilder.New(); @@ -135,7 +214,7 @@ public partial class MainWindow : Window return configBuilder.Build(); } - private void Canvas_OnPointerPressed(object sender, Avalonia.Input.PointerPressedEventArgs e) + private void Canvas_OnPointerPressed(object sender, PointerPressedEventArgs e) { PointerUpdateKind mouseButton = e.GetCurrentPoint((Visual?)sender).Properties.PointerUpdateKind; @@ -156,23 +235,24 @@ public partial class MainWindow : Window }; var buttonOperation = MouseButton.New(buttonType).AsOperationMouseButtonPressed(); - var fastpath = inputDatabase!.Apply(buttonOperation); - var output = activeStage!.ProcessFastpathInput(decodedImage!, fastpath); + var fastpath = _inputDatabase!.Apply(buttonOperation); + var output = _activeStage!.ProcessFastpathInput(_decodedImage!, fastpath); var _ = HandleActiveStageOutput(output); } private void Canvas_PointerMoved(object sender, PointerEventArgs e) { - if (this.activeStage == null || this.decodedImage == null) + if (this._activeStage == null || this._decodedImage == null) { return; } + var position = e.GetPosition((Visual?)sender); var x = (ushort)position.X; var y = (ushort)position.Y; var mouseMovedEvent = MousePosition.New(x, y).AsMoveOperation(); - var fastpath = inputDatabase!.Apply(mouseMovedEvent); - var output = activeStage.ProcessFastpathInput(decodedImage, fastpath); + var fastpath = _inputDatabase!.Apply(mouseMovedEvent); + var output = _activeStage.ProcessFastpathInput(_decodedImage, fastpath); var _ = HandleActiveStageOutput(output); } @@ -197,35 +277,37 @@ public partial class MainWindow : Window }; var buttonOperation = MouseButton.New(buttonType).AsOperationMouseButtonReleased(); - var fastpath = inputDatabase!.Apply(buttonOperation); - var output = activeStage!.ProcessFastpathInput(decodedImage!, fastpath); + var fastpath = _inputDatabase!.Apply(buttonOperation); + var output = _activeStage!.ProcessFastpathInput(_decodedImage!, fastpath); var _ = HandleActiveStageOutput(output); } private void Canvas_KeyDown(object? sender, KeyEventArgs? e) { - if (activeStage == null || decodedImage == null) + if (_activeStage == null || _decodedImage == null) { return; } + PhysicalKey physicalKey = e!.PhysicalKey; var keyOperation = Scancode.FromU16((ushort)KeyCodeMapper.GetScancode(physicalKey)!).AsOperationKeyPressed(); - var fastpath = inputDatabase!.Apply(keyOperation); - var output = activeStage.ProcessFastpathInput(decodedImage, fastpath); + var fastpath = _inputDatabase!.Apply(keyOperation); + var output = _activeStage.ProcessFastpathInput(_decodedImage, fastpath); var _ = HandleActiveStageOutput(output); } private void Canvas_KeyUp(object? sender, KeyEventArgs? e) { - if (this.activeStage == null || this.decodedImage == null) + if (this._activeStage == null || this._decodedImage == null) { return; } + Key key = e!.Key; var keyOperation = Scancode.FromU16((ushort)key).AsOperationKeyReleased(); - var fastpath = inputDatabase!.Apply(keyOperation); - var output = activeStage.ProcessFastpathInput(decodedImage, fastpath); + var fastpath = _inputDatabase!.Apply(keyOperation); + var output = _activeStage.ProcessFastpathInput(_decodedImage, fastpath); var _ = HandleActiveStageOutput(output); } @@ -233,10 +315,10 @@ public partial class MainWindow : Window { try { - while (!outputIterator.IsEmpty()) { - var output = outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false + var output = + outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false if (output.GetEnumType() == ActiveStageOutputType.Terminate) { return false; @@ -246,10 +328,10 @@ public partial class MainWindow : Window // render the decoded image to canvas WriteDecodedImageToCanvas(); // Send the response frame to the server - var responseFrame = output.GetResponseFrame()!; + var responseFrame = output.GetResponseFrame(); byte[] responseFrameBytes = new byte[responseFrame.GetSize()]; responseFrame.Fill(responseFrameBytes); - await framed!.Write(responseFrameBytes); + await _framed!.Write(responseFrameBytes); } else if (output.GetEnumType() == ActiveStageOutputType.GraphicsUpdate) { @@ -264,12 +346,23 @@ public partial class MainWindow : Window WriteDecodedImageToCanvas(); } } + return true; } - catch (Exception e) + catch (Exception) { return false; } } -} + IntPtr? GetWindowHandle() + { + var handle = this.TryGetPlatformHandle(); + if (handle == null) + { + return null; + } + + return handle.Handle; + } +} \ No newline at end of file diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/packages-microsoft-prod.deb b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/packages-microsoft-prod.deb new file mode 100644 index 00000000..14e3c628 Binary files /dev/null and b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/packages-microsoft-prod.deb differ diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ActiveStage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ActiveStage.cs index 105e06c1..dc9fdf32 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ActiveStage.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ActiveStage.cs @@ -125,6 +125,90 @@ public partial class ActiveStage: IDisposable } } + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 InitiateClipboardCopy(ClipboardFormatIterator formats) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ActiveStage"); + } + Raw.ClipboardFormatIterator* formatsRaw; + formatsRaw = formats.AsFFI(); + if (formatsRaw == null) + { + throw new ObjectDisposedException("ClipboardFormatIterator"); + } + Raw.SessionFfiResultBoxVecU8BoxIronRdpError result = Raw.ActiveStage.InitiateClipboardCopy(_inner, formatsRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 InitiateClipboardPaste(ClipboardFormatId formatId) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ActiveStage"); + } + Raw.ClipboardFormatId* formatIdRaw; + formatIdRaw = formatId.AsFFI(); + if (formatIdRaw == null) + { + throw new ObjectDisposedException("ClipboardFormatId"); + } + Raw.SessionFfiResultBoxVecU8BoxIronRdpError result = Raw.ActiveStage.InitiateClipboardPaste(_inner, formatIdRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + + /// + /// + /// A VecU8 allocated on Rust side. + /// + public VecU8 SubmitClipboardFormatData(FormatDataResponse formatDataResponse) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ActiveStage"); + } + Raw.FormatDataResponse* formatDataResponseRaw; + formatDataResponseRaw = formatDataResponse.AsFFI(); + if (formatDataResponseRaw == null) + { + throw new ObjectDisposedException("FormatDataResponse"); + } + Raw.SessionFfiResultBoxVecU8BoxIronRdpError result = Raw.ActiveStage.SubmitClipboardFormatData(_inner, formatDataResponseRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.VecU8* retVal = result.Ok; + return new VecU8(retVal); + } + } + /// /// Returns the underlying raw handle. /// diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs index bcb08f4c..23345d24 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClientConnector.cs @@ -257,6 +257,29 @@ public partial class ClientConnector: IDisposable } } + /// + public void AttachStaticCliprdr(Cliprdr cliprdr) + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClientConnector"); + } + Raw.Cliprdr* cliprdrRaw; + cliprdrRaw = cliprdr.AsFFI(); + if (cliprdrRaw == null) + { + throw new ObjectDisposedException("Cliprdr"); + } + Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.AttachStaticCliprdr(_inner, cliprdrRaw); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + } + } + /// /// /// A PduHint allocated on Rust side. diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatId.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatId.cs new file mode 100644 index 00000000..29fdadac --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatId.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClipboardFormatId: IDisposable +{ + private unsafe Raw.ClipboardFormatId* _inner; + + /// + /// Creates a managed ClipboardFormatId from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClipboardFormatId(Raw.ClipboardFormatId* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClipboardFormatId* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClipboardFormatId.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClipboardFormatId() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatIterator.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatIterator.cs new file mode 100644 index 00000000..1c44bb9d --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardFormatIterator.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClipboardFormatIterator: IDisposable +{ + private unsafe Raw.ClipboardFormatIterator* _inner; + + /// + /// Creates a managed ClipboardFormatIterator from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClipboardFormatIterator(Raw.ClipboardFormatIterator* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClipboardFormatIterator* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClipboardFormatIterator.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClipboardFormatIterator() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessage.cs new file mode 100644 index 00000000..5d0441ce --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessage.cs @@ -0,0 +1,171 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClipboardMessage: IDisposable +{ + private unsafe Raw.ClipboardMessage* _inner; + + public ClipboardMessageType MessageType + { + get + { + return GetMessageType(); + } + } + + public FormatDataResponse? SendFormatData + { + get + { + return GetSendFormatData(); + } + } + + public ClipboardFormatIterator? SendInitiateCopy + { + get + { + return GetSendInitiateCopy(); + } + } + + public ClipboardFormatId? SendInitiatePaste + { + get + { + return GetSendInitiatePaste(); + } + } + + /// + /// Creates a managed ClipboardMessage from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClipboardMessage(Raw.ClipboardMessage* handle) + { + _inner = handle; + } + + /// + /// A ClipboardMessageType allocated on C# side. + /// + public ClipboardMessageType GetMessageType() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClipboardMessage"); + } + Raw.ClipboardMessageType retVal = Raw.ClipboardMessage.GetMessageType(_inner); + return (ClipboardMessageType)retVal; + } + } + + /// + /// A ClipboardFormatIterator allocated on Rust side. + /// + public ClipboardFormatIterator? GetSendInitiateCopy() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClipboardMessage"); + } + Raw.ClipboardFormatIterator* retVal = Raw.ClipboardMessage.GetSendInitiateCopy(_inner); + if (retVal == null) + { + return null; + } + return new ClipboardFormatIterator(retVal); + } + } + + /// + /// A FormatDataResponse allocated on Rust side. + /// + public FormatDataResponse? GetSendFormatData() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClipboardMessage"); + } + Raw.FormatDataResponse* retVal = Raw.ClipboardMessage.GetSendFormatData(_inner); + if (retVal == null) + { + return null; + } + return new FormatDataResponse(retVal); + } + } + + /// + /// A ClipboardFormatId allocated on Rust side. + /// + public ClipboardFormatId? GetSendInitiatePaste() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("ClipboardMessage"); + } + Raw.ClipboardFormatId* retVal = Raw.ClipboardMessage.GetSendInitiatePaste(_inner); + if (retVal == null) + { + return null; + } + return new ClipboardFormatId(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClipboardMessage* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClipboardMessage.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClipboardMessage() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessageType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessageType.cs new file mode 100644 index 00000000..8c2b901e --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardMessageType.cs @@ -0,0 +1,20 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public enum ClipboardMessageType +{ + SendInitiateCopy = 0, + SendFormatData = 1, + SendInitiatePaste = 2, + Error = 3, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardSvgMessage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardSvgMessage.cs new file mode 100644 index 00000000..676f4ac0 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/ClipboardSvgMessage.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class ClipboardSvgMessage: IDisposable +{ + private unsafe Raw.ClipboardSvgMessage* _inner; + + /// + /// Creates a managed ClipboardSvgMessage from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe ClipboardSvgMessage(Raw.ClipboardSvgMessage* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.ClipboardSvgMessage* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.ClipboardSvgMessage.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~ClipboardSvgMessage() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Cliprdr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Cliprdr.cs new file mode 100644 index 00000000..c2bff3d1 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Cliprdr.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class Cliprdr: IDisposable +{ + private unsafe Raw.Cliprdr* _inner; + + /// + /// Creates a managed Cliprdr from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Cliprdr(Raw.Cliprdr* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Cliprdr* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Cliprdr.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Cliprdr() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/CliprdrBackendFactory.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/CliprdrBackendFactory.cs new file mode 100644 index 00000000..3ca080c2 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/CliprdrBackendFactory.cs @@ -0,0 +1,79 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class CliprdrBackendFactory: IDisposable +{ + private unsafe Raw.CliprdrBackendFactory* _inner; + + /// + /// Creates a managed CliprdrBackendFactory from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe CliprdrBackendFactory(Raw.CliprdrBackendFactory* handle) + { + _inner = handle; + } + + /// + /// A Cliprdr allocated on Rust side. + /// + public Cliprdr BuildCliprdr() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("CliprdrBackendFactory"); + } + Raw.Cliprdr* retVal = Raw.CliprdrBackendFactory.BuildCliprdr(_inner); + return new Cliprdr(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.CliprdrBackendFactory* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.CliprdrBackendFactory.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~CliprdrBackendFactory() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/FormatDataResponse.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/FormatDataResponse.cs new file mode 100644 index 00000000..afeff1bc --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/FormatDataResponse.cs @@ -0,0 +1,63 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class FormatDataResponse: IDisposable +{ + private unsafe Raw.FormatDataResponse* _inner; + + /// + /// Creates a managed FormatDataResponse from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe FormatDataResponse(Raw.FormatDataResponse* handle) + { + _inner = handle; + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.FormatDataResponse* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.FormatDataResponse.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~FormatDataResponse() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs index 2b20bdac..bb2d219c 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/IronRdpErrorKind.cs @@ -20,4 +20,6 @@ public enum IronRdpErrorKind IO = 4, AccessDenied = 5, IncorrectEnumType = 6, + Clipboard = 7, + WrongOS = 8, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawActiveStage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawActiveStage.cs index ddda9e5e..b41c328a 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawActiveStage.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawActiveStage.cs @@ -25,6 +25,15 @@ public partial struct ActiveStage [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ActiveStage_process_fastpath_input", ExactSpelling = true)] public static unsafe extern SessionFfiResultBoxActiveStageOutputIteratorBoxIronRdpError ProcessFastpathInput(ActiveStage* self, DecodedImage* image, FastPathInputEventIterator* fastpathInput); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ActiveStage_initiate_clipboard_copy", ExactSpelling = true)] + public static unsafe extern SessionFfiResultBoxVecU8BoxIronRdpError InitiateClipboardCopy(ActiveStage* self, ClipboardFormatIterator* formats); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ActiveStage_initiate_clipboard_paste", ExactSpelling = true)] + public static unsafe extern SessionFfiResultBoxVecU8BoxIronRdpError InitiateClipboardPaste(ActiveStage* self, ClipboardFormatId* formatId); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ActiveStage_submit_clipboard_format_data", ExactSpelling = true)] + public static unsafe extern SessionFfiResultBoxVecU8BoxIronRdpError SubmitClipboardFormatData(ActiveStage* self, FormatDataResponse* formatDataResponse); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ActiveStage_destroy", ExactSpelling = true)] public static unsafe extern void Destroy(ActiveStage* self); } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs index ee4a2445..b1cff30f 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClientConnector.cs @@ -55,6 +55,9 @@ public partial struct ClientConnector [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_step_no_input", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultBoxWrittenBoxIronRdpError StepNoInput(ClientConnector* self, WriteBuf* writeBuf); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_attach_static_cliprdr", ExactSpelling = true)] + public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError AttachStaticCliprdr(ClientConnector* self, Cliprdr* cliprdr); + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_next_pdu_hint", ExactSpelling = true)] public static unsafe extern ConnectorFfiResultOptBoxPduHintBoxIronRdpError NextPduHint(ClientConnector* self); diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatId.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatId.cs new file mode 100644 index 00000000..cd17daed --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatId.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardFormatId +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardFormatId_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClipboardFormatId* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatIterator.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatIterator.cs new file mode 100644 index 00000000..f11d7e0a --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardFormatIterator.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardFormatIterator +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardFormatIterator_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClipboardFormatIterator* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessage.cs new file mode 100644 index 00000000..e674a18f --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessage.cs @@ -0,0 +1,33 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardMessage +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardMessage_get_message_type", ExactSpelling = true)] + public static unsafe extern ClipboardMessageType GetMessageType(ClipboardMessage* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardMessage_get_send_initiate_copy", ExactSpelling = true)] + public static unsafe extern ClipboardFormatIterator* GetSendInitiateCopy(ClipboardMessage* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardMessage_get_send_format_data", ExactSpelling = true)] + public static unsafe extern FormatDataResponse* GetSendFormatData(ClipboardMessage* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardMessage_get_send_initiate_paste", ExactSpelling = true)] + public static unsafe extern ClipboardFormatId* GetSendInitiatePaste(ClipboardMessage* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardMessage_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClipboardMessage* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessageType.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessageType.cs new file mode 100644 index 00000000..17c6ac37 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardMessageType.cs @@ -0,0 +1,20 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +public enum ClipboardMessageType +{ + SendInitiateCopy = 0, + SendFormatData = 1, + SendInitiatePaste = 2, + Error = 3, +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardSvgMessage.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardSvgMessage.cs new file mode 100644 index 00000000..77001bad --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardSvgMessage.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardSvgMessage +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClipboardSvgMessage_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(ClipboardSvgMessage* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError.cs new file mode 100644 index 00000000..52423479 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ClipboardMessage* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ClipboardMessage* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError.cs new file mode 100644 index 00000000..6273f969 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal CliprdrBackendFactory* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe CliprdrBackendFactory* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError.cs new file mode 100644 index 00000000..d6ee33da --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal WinCliprdr* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe WinCliprdr* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError.cs new file mode 100644 index 00000000..d8f1c9b8 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct ClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal ClipboardMessage* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe ClipboardMessage* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdr.cs new file mode 100644 index 00000000..7db8d1f3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdr.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Cliprdr +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Cliprdr_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Cliprdr* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdrBackendFactory.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdrBackendFactory.cs new file mode 100644 index 00000000..4fa04c30 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawCliprdrBackendFactory.cs @@ -0,0 +1,24 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct CliprdrBackendFactory +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CliprdrBackendFactory_build_cliprdr", ExactSpelling = true)] + public static unsafe extern Cliprdr* BuildCliprdr(CliprdrBackendFactory* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "CliprdrBackendFactory_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(CliprdrBackendFactory* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawFormatDataResponse.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawFormatDataResponse.cs new file mode 100644 index 00000000..3d85b3f3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawFormatDataResponse.cs @@ -0,0 +1,21 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct FormatDataResponse +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "FormatDataResponse_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(FormatDataResponse* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs index 045a9aed..d9056006 100644 --- a/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawIronRdpErrorKind.cs @@ -20,4 +20,6 @@ public enum IronRdpErrorKind IO = 4, AccessDenied = 5, IncorrectEnumType = 6, + Clipboard = 7, + WrongOS = 8, } diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawSessionFfiResultBoxVecU8BoxIronRdpError.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSessionFfiResultBoxVecU8BoxIronRdpError.cs new file mode 100644 index 00000000..a3b9a0e6 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawSessionFfiResultBoxVecU8BoxIronRdpError.cs @@ -0,0 +1,46 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct SessionFfiResultBoxVecU8BoxIronRdpError +{ + [StructLayout(LayoutKind.Explicit)] + private unsafe struct InnerUnion + { + [FieldOffset(0)] + internal VecU8* ok; + [FieldOffset(0)] + internal IronRdpError* err; + } + + private InnerUnion _inner; + + [MarshalAs(UnmanagedType.U1)] + public bool isOk; + + public unsafe VecU8* Ok + { + get + { + return _inner.ok; + } + } + + public unsafe IronRdpError* Err + { + get + { + return _inner.err; + } + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawWinCliprdr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWinCliprdr.cs new file mode 100644 index 00000000..28057334 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawWinCliprdr.cs @@ -0,0 +1,36 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct WinCliprdr +{ + private const string NativeLib = "DevolutionsIronRdp"; + + /// + /// SAFETY: `hwnd` must be a valid window handle + /// + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WinCliprdr_new", ExactSpelling = true)] + public static unsafe extern ClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError New(nint hwnd); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WinCliprdr_next_clipboard_message", ExactSpelling = true)] + public static unsafe extern ClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError NextClipboardMessage(WinCliprdr* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WinCliprdr_next_clipboard_message_blocking", ExactSpelling = true)] + public static unsafe extern ClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError NextClipboardMessageBlocking(WinCliprdr* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WinCliprdr_backend_factory", ExactSpelling = true)] + public static unsafe extern ClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError BackendFactory(WinCliprdr* self); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WinCliprdr_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(WinCliprdr* self); +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/WinCliprdr.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/WinCliprdr.cs new file mode 100644 index 00000000..a8ead2a8 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/WinCliprdr.cs @@ -0,0 +1,154 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class WinCliprdr: IDisposable +{ + private unsafe Raw.WinCliprdr* _inner; + + /// + /// Creates a managed WinCliprdr from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe WinCliprdr(Raw.WinCliprdr* handle) + { + _inner = handle; + } + + /// + /// SAFETY: `hwnd` must be a valid window handle + /// + /// + /// + /// A WinCliprdr allocated on Rust side. + /// + public static WinCliprdr New(nint hwnd) + { + unsafe + { + Raw.ClipboardWindowsFfiResultBoxWinCliprdrBoxIronRdpError result = Raw.WinCliprdr.New(hwnd); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.WinCliprdr* retVal = result.Ok; + return new WinCliprdr(retVal); + } + } + + /// + /// + /// A ClipboardMessage allocated on Rust side. + /// + public ClipboardMessage NextClipboardMessage() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("WinCliprdr"); + } + Raw.ClipboardWindowsFfiResultOptBoxClipboardMessageBoxIronRdpError result = Raw.WinCliprdr.NextClipboardMessage(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ClipboardMessage* retVal = result.Ok; + if (retVal == null) + { + return null; + } + return new ClipboardMessage(retVal); + } + } + + /// + /// + /// A ClipboardMessage allocated on Rust side. + /// + public ClipboardMessage NextClipboardMessageBlocking() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("WinCliprdr"); + } + Raw.ClipboardWindowsFfiResultBoxClipboardMessageBoxIronRdpError result = Raw.WinCliprdr.NextClipboardMessageBlocking(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.ClipboardMessage* retVal = result.Ok; + return new ClipboardMessage(retVal); + } + } + + /// + /// + /// A CliprdrBackendFactory allocated on Rust side. + /// + public CliprdrBackendFactory BackendFactory() + { + unsafe + { + if (_inner == null) + { + throw new ObjectDisposedException("WinCliprdr"); + } + Raw.ClipboardWindowsFfiResultBoxCliprdrBackendFactoryBoxIronRdpError result = Raw.WinCliprdr.BackendFactory(_inner); + if (!result.isOk) + { + throw new IronRdpException(new IronRdpError(result.Err)); + } + Raw.CliprdrBackendFactory* retVal = result.Ok; + return new CliprdrBackendFactory(retVal); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.WinCliprdr* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.WinCliprdr.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~WinCliprdr() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs b/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs index 3581e84d..74df6571 100644 --- a/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs +++ b/ffi/dotnet/Devolutions.IronRdp/src/Connection.cs @@ -2,12 +2,12 @@ using System.Net; using System.Net.Security; using System.Net.Sockets; -using Devolutions.IronRdp; -public class Connection +namespace Devolutions.IronRdp; +public static class Connection { - public static async Task<(ConnectionResult, Framed)> Connect(Config config, string servername) + public static async Task<(ConnectionResult, Framed)> Connect(Config config, string servername, CliprdrBackendFactory? factory) { var stream = await CreateTcpConnection(servername, 3389); @@ -23,6 +23,12 @@ public class Connection var socketAddrString = ip[0].ToString() + ":3389"; connector.WithServerAddr(socketAddrString); + + if (factory != null) + { + var cliprdr = factory.BuildCliprdr(); + connector.AttachStaticCliprdr(cliprdr); + } await connectBegin(framed, connector); var (serverPublicKey, framedSsl) = await securityUpgrade(servername, framed, connector); @@ -104,7 +110,7 @@ public class Connection await framedSsl.Write(response); } - var pduHint = credsspSequence.NextPduHint()!; + var pduHint = credsspSequence.NextPduHint(); if (pduHint == null) { break; @@ -144,7 +150,7 @@ public class Connection } if (protocol == NetworkRequestProtocol.Tcp) { - stream.Write(Utils.Vecu8ToByte(data)); + stream.Write(Utils.VecU8ToByte(data)); var readBuf = new byte[8096]; var readlen = await stream.ReadAsync(readBuf, 0, readBuf.Length); var actuallyRead = new byte[readlen]; @@ -158,8 +164,8 @@ public class Connection } else { - var client_state = state.GetClientStateIfCompleted(); - return client_state; + var clientState = state.GetClientStateIfCompleted(); + return clientState; } } } @@ -223,7 +229,7 @@ public class Connection public static class Utils { - public static byte[] Vecu8ToByte(VecU8 vecU8) + public static byte[] VecU8ToByte(VecU8 vecU8) { var len = vecU8.GetSize(); byte[] buffer = new byte[len]; diff --git a/ffi/src/clipboard/message.rs b/ffi/src/clipboard/message.rs new file mode 100644 index 00000000..1ab5d003 --- /dev/null +++ b/ffi/src/clipboard/message.rs @@ -0,0 +1,65 @@ +#[diplomat::bridge] +pub mod ffi { + + #[diplomat::opaque] + pub struct ClipboardMessage(pub ironrdp::cliprdr::backend::ClipboardMessage); + + impl ClipboardMessage { + pub fn get_message_type(&self) -> ClipboardMessageType { + match &self.0 { + ironrdp::cliprdr::backend::ClipboardMessage::SendInitiateCopy(_) => { + ClipboardMessageType::SendInitiateCopy + } + ironrdp::cliprdr::backend::ClipboardMessage::SendFormatData(_) => ClipboardMessageType::SendFormatData, + ironrdp::cliprdr::backend::ClipboardMessage::SendInitiatePaste(_) => { + ClipboardMessageType::SendInitiatePaste + } + ironrdp::cliprdr::backend::ClipboardMessage::Error(_) => ClipboardMessageType::Error, + } + } + + pub fn get_send_initiate_copy(&self) -> Option> { + match &self.0 { + ironrdp::cliprdr::backend::ClipboardMessage::SendInitiateCopy(val) => Some(val.clone()), + _ => None, + } + .map(ClipboardFormatIterator) + .map(Box::new) + } + + pub fn get_send_format_data(&self) -> Option> { + match &self.0 { + ironrdp::cliprdr::backend::ClipboardMessage::SendFormatData(val) => Some(val.clone()), + _ => None, + } + .map(Some) + .map(FormatDataResponse) + .map(Box::new) + } + + pub fn get_send_initiate_paste(&self) -> Option> { + match &self.0 { + ironrdp::cliprdr::backend::ClipboardMessage::SendInitiatePaste(val) => Some(*val), + _ => None, + } + .map(ClipboardFormatId) + .map(Box::new) + } + } + + pub enum ClipboardMessageType { + SendInitiateCopy, + SendFormatData, + SendInitiatePaste, + Error, + } + + #[diplomat::opaque] + pub struct ClipboardFormatIterator(pub Vec); + + #[diplomat::opaque] + pub struct FormatDataResponse(pub Option); + + #[diplomat::opaque] + pub struct ClipboardFormatId(pub ironrdp::cliprdr::pdu::ClipboardFormatId); +} diff --git a/ffi/src/clipboard/mod.rs b/ffi/src/clipboard/mod.rs new file mode 100644 index 00000000..6eb8b767 --- /dev/null +++ b/ffi/src/clipboard/mod.rs @@ -0,0 +1,39 @@ +pub mod message; + +pub mod windows; + +#[diplomat::bridge] +pub mod ffi { + + use ironrdp::cliprdr::Client; + + #[diplomat::opaque] + pub struct CliprdrBackendFactory(pub Box); + + impl CliprdrBackendFactory { + pub fn build_cliprdr(&self) -> Box { + let backend = self.0.build_cliprdr_backend(); + let cliprdr = ironrdp::cliprdr::Cliprdr::new(backend); + Box::new(Cliprdr(Some(cliprdr))) + } + } + + #[diplomat::opaque] + pub struct Cliprdr(pub Option>); + + #[diplomat::opaque] + pub struct ClipboardSvgMessage(pub Option>); +} + +#[derive(Debug)] +pub struct FfiClipbarodMessageProxy { + pub sender: std::sync::mpsc::Sender, +} + +impl ironrdp::cliprdr::backend::ClipboardMessageProxy for FfiClipbarodMessageProxy { + fn send_clipboard_message(&self, message: ironrdp::cliprdr::backend::ClipboardMessage) { + if let Err(err) = self.sender.send(message) { + tracing::error!("Failed to send clipboard message: {:?}", err); + } + } +} diff --git a/ffi/src/clipboard/windows.rs b/ffi/src/clipboard/windows.rs new file mode 100644 index 00000000..a9c5f530 --- /dev/null +++ b/ffi/src/clipboard/windows.rs @@ -0,0 +1,124 @@ +#![allow(clippy::unused_self)] // We want to keep the signature of the function stay the same between windows and non-windows + +use super::ffi::CliprdrBackendFactory; +use crate::error::ffi::IronRdpError; +#[cfg(not(windows))] +use crate::error::WrongOSError; +#[cfg(not(windows))] +use ironrdp_cliprdr_native as _; // avoid linter error, stub clipboard will be used in later commit + +/* + Why are we creating a WinCliprdrInner struct and implement differently? + + 1. We want to keep the FFI interface align with generated bindings. + 2. ironrdp_cliprdr_native::WinClipboard only compiles if the target is windows. + 3. We do not want to put any conditional compilation in the ffi module. + + Hence we create a WinCliprdrInner struct and implement it differently based on the target platform. + and throw WrongOSError if the target platform is not windows. +*/ +#[diplomat::bridge] +pub mod ffi { + + use crate::clipboard::ffi::CliprdrBackendFactory; + use crate::clipboard::message::ffi::ClipboardMessage; + use crate::error::ffi::IronRdpError; + + use super::WinCliprdrInner; + + #[diplomat::opaque] + pub struct WinCliprdr(WinCliprdrInner); + + impl WinCliprdr { + /// SAFETY: `hwnd` must be a valid window handle + pub fn new(hwnd: isize) -> Result, Box> { + WinCliprdrInner::new(hwnd).map(WinCliprdr).map(Box::new) + } + + pub fn next_clipboard_message(&self) -> Result>, Box> { + Ok(self.0.next_clipboard_message()?.map(ClipboardMessage).map(Box::new)) + } + + pub fn next_clipboard_message_blocking(&self) -> Result, Box> { + self.0 + .next_clipboard_message_blocking() + .map(ClipboardMessage) + .map(Box::new) + } + + pub fn backend_factory(&self) -> Result, Box> { + self.0.backend_factory().map(Box::new) + } + } +} + +#[cfg(not(windows))] +pub struct WinCliprdrInner; + +#[cfg(not(windows))] +impl WinCliprdrInner { + fn new(_hwnd: isize) -> Result> { + Err(WrongOSError::expected_platform("windows") + .with_custom_message("WinCliprdr only support windows") + .into()) + } + + fn next_clipboard_message(&self) -> Result, Box> { + Err(WrongOSError::expected_platform("windows") + .with_custom_message("WinCliprdr only support windows") + .into()) + } + + fn backend_factory(&self) -> Result> { + Err(WrongOSError::expected_platform("windows") + .with_custom_message("WinCliprdr only support windows") + .into()) + } + + fn next_clipboard_message_blocking( + &self, + ) -> Result> { + Err(WrongOSError::expected_platform("windows") + .with_custom_message("WinCliprdr only support windows") + .into()) + } +} + +#[cfg(windows)] +pub struct WinCliprdrInner { + pub clipboard: ironrdp_cliprdr_native::WinClipboard, + pub receiver: std::sync::mpsc::Receiver, +} + +#[cfg(windows)] +impl WinCliprdrInner { + fn new(hwnd: isize) -> Result> { + use windows::Win32::Foundation::HWND; + + let (sender, receiver) = std::sync::mpsc::channel(); + + let proxy = crate::clipboard::FfiClipbarodMessageProxy { sender }; + + // SAFETY: `hwnd` must be a valid window handle + let clipboard = unsafe { ironrdp_cliprdr_native::WinClipboard::new(HWND(hwnd), proxy) }?; + + Ok(WinCliprdrInner { clipboard, receiver }) + } + + fn next_clipboard_message(&self) -> Result, Box> { + Ok(self.receiver.try_recv().ok()) + } + + fn backend_factory(&self) -> Result> { + Ok(CliprdrBackendFactory(self.clipboard.backend_factory())) + } + + fn next_clipboard_message_blocking( + &self, + ) -> Result> { + Ok(self + .receiver + .recv() + .map_err(|_| "Failed to receive clipboard message")?) + } +} diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 8134847b..700d1e17 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -9,6 +9,7 @@ pub mod ffi { use std::fmt::Write; use crate::{ + clipboard::ffi::Cliprdr, error::{ ffi::{IronRdpError, IronRdpErrorKind}, ValueConsumedError, @@ -118,6 +119,19 @@ pub mod ffi { let written = connector.step_no_input(&mut write_buf.0)?; Ok(Box::new(Written(written))) } + + pub fn attach_static_cliprdr(&mut self, cliprdr: &mut Cliprdr) -> Result<(), Box> { + let Some(connector) = self.0.as_mut() else { + return Err(ValueConsumedError::for_item("connector").into()); + }; + + let Some(cliprdr) = cliprdr.0.take() else { + return Err(ValueConsumedError::for_item("cliprdr").into()); + }; + + connector.attach_static_channel(cliprdr); + Ok(()) + } } #[diplomat::opaque] diff --git a/ffi/src/error.rs b/ffi/src/error.rs index 05e26c83..ece5663e 100644 --- a/ffi/src/error.rs +++ b/ffi/src/error.rs @@ -1,7 +1,9 @@ #![allow(clippy::return_self_not_must_use)] use std::fmt::Display; -use ironrdp::{connector::ConnectorError, session::SessionError}; +use ironrdp::{cliprdr::backend::ClipboardError, connector::ConnectorError, session::SessionError}; +#[cfg(target_os = "windows")] +use ironrdp_cliprdr_native::WinCliprdrError; use self::ffi::IronRdpErrorKind; @@ -55,6 +57,25 @@ impl From for IronRdpErrorKind { } } +impl From<&dyn ClipboardError> for IronRdpErrorKind { + fn from(_val: &dyn ClipboardError) -> Self { + IronRdpErrorKind::Clipboard + } +} + +#[cfg(target_os = "windows")] +impl From for IronRdpErrorKind { + fn from(_val: WinCliprdrError) -> Self { + IronRdpErrorKind::Clipboard + } +} + +impl From for IronRdpErrorKind { + fn from(_val: WrongOSError) -> Self { + IronRdpErrorKind::WrongOS + } +} + impl From for Box where T: Into + ToString, @@ -92,6 +113,10 @@ pub mod ffi { AccessDenied, #[error("Incorrect rust enum type")] IncorrectEnumType, + #[error("Clipboard error")] + Clipboard, + #[error("wrong platform error")] + WrongOS, } /// Stringified Picky error along with an error kind. @@ -187,3 +212,31 @@ impl From for IronRdpErrorKind { IronRdpErrorKind::IncorrectEnumType } } + +pub struct WrongOSError { + expected: &'static str, + custom_message: Option, +} + +impl WrongOSError { + pub fn expected_platform(expected: &'static str) -> WrongOSError { + WrongOSError { + expected, + custom_message: None, + } + } + + pub fn with_custom_message(mut self, message: &str) -> WrongOSError { + self.custom_message = Some(message.to_owned()); + self + } +} + +impl Display for WrongOSError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(custom_message) = &self.custom_message { + write!(f, "{}", custom_message)?; + } + write!(f, "expected platform {}", self.expected) + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e5eaaf56..407847ce 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1,6 +1,8 @@ #![allow(clippy::unnecessary_box_returns)] // Diplomat requires returning Boxed types #![allow(clippy::should_implement_trait)] // Implementing extra traits is not useful for FFI +#![allow(clippy::needless_lifetimes)] // Diplomat requires lifetimes to be specified even if they can be elided in regular Rust code +pub mod clipboard; pub mod connector; pub mod credssp; pub mod dvc; diff --git a/ffi/src/session/mod.rs b/ffi/src/session/mod.rs index f4aca56a..2a8d3ed1 100644 --- a/ffi/src/session/mod.rs +++ b/ffi/src/session/mod.rs @@ -4,11 +4,12 @@ pub mod image; pub mod ffi { use crate::{ + clipboard::message::ffi::{ClipboardFormatId, ClipboardFormatIterator, FormatDataResponse}, connector::{ffi::ConnectionActivationSequence, result::ffi::ConnectionResult}, error::{ffi::IronRdpError, IncorrectEnumTypeError, ValueConsumedError}, graphics::ffi::DecodedPointer, pdu::ffi::{Action, FastPathInputEventIterator, InclusiveRectangle}, - utils::ffi::{BytesSlice, Position}, + utils::ffi::{BytesSlice, Position, VecU8}, }; use super::image::ffi::DecodedImage; @@ -66,6 +67,60 @@ pub mod ffi { .process_fastpath_input(&mut image.0, &fastpath_input.0) .map(|outputs| Box::new(ActiveStageOutputIterator(outputs)))?) } + + pub fn initiate_clipboard_copy( + &mut self, + formats: &ClipboardFormatIterator, + ) -> Result, Box> { + let formats = formats.0.clone(); + let clipboard = self + .0 + .get_svc_processor::() + .ok_or("clipboard svc processor not found in active stage")?; + + let result = clipboard.initiate_copy(&formats)?; + + let frame = self.0.process_svc_processor_messages(result)?; + + Ok(Box::new(VecU8(frame))) + } + + pub fn initiate_clipboard_paste( + &mut self, + format_id: &ClipboardFormatId, + ) -> Result, Box> { + let format_id = format_id.0; + let clipboard = self + .0 + .get_svc_processor::() + .ok_or("clipboard svc processor not found in active stage")?; + + let result = clipboard.initiate_paste(format_id)?; + + let frame = self.0.process_svc_processor_messages(result)?; + + Ok(Box::new(VecU8(frame))) + } + + pub fn submit_clipboard_format_data( + &mut self, + format_data_response: &mut FormatDataResponse, + ) -> Result, Box> { + let data = format_data_response + .0 + .take() + .ok_or_else(|| ValueConsumedError::for_item("format_data_response"))?; + let clipboard = self + .0 + .get_svc_processor::() + .ok_or("clipboard svc processor not found in active stage")?; + + let result = clipboard.submit_format_data(data)?; + + let frame = self.0.process_svc_processor_messages(result)?; + + Ok(Box::new(VecU8(frame))) + } } pub enum ActiveStageOutputType {