mirror of
https://github.com/netbirdio/IronRDP.git
synced 2026-05-22 18:43:12 -07:00
feat(ffi): expose cliprdr to dotnet bindings (#459)
This commit is contained in:
Generated
+2
@@ -1134,10 +1134,12 @@ dependencies = [
|
||||
"diplomat-runtime",
|
||||
"embed-resource",
|
||||
"ironrdp",
|
||||
"ironrdp-cliprdr-native",
|
||||
"sspi",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+7
-1
@@ -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",
|
||||
] }
|
||||
+11
-2
@@ -9,13 +9,22 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.10" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<!--Condition
|
||||
below is needed to remove Avalonia.Diagnostics package from build output in Release
|
||||
configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||
<ProjectReference Include="../Devolutions.IronRdp/Devolutions.IronRdp.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
<ItemGroup Condition="'$([System.OperatingSystem]::IsWindows())'">
|
||||
<PackageReference Include="Avalonia.Win32" Version="11.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -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<SslStream>? _framed;
|
||||
WinCliprdr? _cliprdr;
|
||||
|
||||
WriteableBitmap? bitmap;
|
||||
Canvas? canvas;
|
||||
Image? image;
|
||||
InputDatabase? inputDatabase = InputDatabase.New();
|
||||
ActiveStage? activeStage;
|
||||
DecodedImage? decodedImage;
|
||||
Framed<SslStream>? 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<Canvas>("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<Canvas>("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;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -125,6 +125,90 @@ public partial class ActiveStage: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
/// <returns>
|
||||
/// A <c>VecU8</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
/// <returns>
|
||||
/// A <c>VecU8</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
/// <returns>
|
||||
/// A <c>VecU8</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
|
||||
@@ -257,6 +257,29 @@ public partial class ClientConnector: IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
/// <returns>
|
||||
/// A <c>PduHint</c> allocated on Rust side.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>ClipboardFormatId</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe ClipboardFormatId(Raw.ClipboardFormatId* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.ClipboardFormatId* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.ClipboardFormatId.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ClipboardFormatId()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>ClipboardFormatIterator</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe ClipboardFormatIterator(Raw.ClipboardFormatIterator* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.ClipboardFormatIterator* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.ClipboardFormatIterator.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ClipboardFormatIterator()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// <auto-generated/> 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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>ClipboardMessage</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe ClipboardMessage(Raw.ClipboardMessage* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// A <c>ClipboardMessageType</c> allocated on C# side.
|
||||
/// </returns>
|
||||
public ClipboardMessageType GetMessageType()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
throw new ObjectDisposedException("ClipboardMessage");
|
||||
}
|
||||
Raw.ClipboardMessageType retVal = Raw.ClipboardMessage.GetMessageType(_inner);
|
||||
return (ClipboardMessageType)retVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// A <c>ClipboardFormatIterator</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// A <c>FormatDataResponse</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// A <c>ClipboardFormatId</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.ClipboardMessage* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.ClipboardMessage.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ClipboardMessage()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// <auto-generated/> 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,
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>ClipboardSvgMessage</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe ClipboardSvgMessage(Raw.ClipboardSvgMessage* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.ClipboardSvgMessage* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.ClipboardSvgMessage.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ClipboardSvgMessage()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>Cliprdr</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe Cliprdr(Raw.Cliprdr* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.Cliprdr* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.Cliprdr.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~Cliprdr()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>CliprdrBackendFactory</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe CliprdrBackendFactory(Raw.CliprdrBackendFactory* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <returns>
|
||||
/// A <c>Cliprdr</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
public Cliprdr BuildCliprdr()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
throw new ObjectDisposedException("CliprdrBackendFactory");
|
||||
}
|
||||
Raw.Cliprdr* retVal = Raw.CliprdrBackendFactory.BuildCliprdr(_inner);
|
||||
return new Cliprdr(retVal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.CliprdrBackendFactory* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.CliprdrBackendFactory.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~CliprdrBackendFactory()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// <auto-generated/> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a managed <c>FormatDataResponse</c> from a raw handle.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free).
|
||||
/// <br/>
|
||||
/// This constructor assumes the raw struct is allocated on Rust side.
|
||||
/// If implemented, the custom Drop implementation on Rust side WILL run on destruction.
|
||||
/// </remarks>
|
||||
public unsafe FormatDataResponse(Raw.FormatDataResponse* handle)
|
||||
{
|
||||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying raw handle.
|
||||
/// </summary>
|
||||
public unsafe Raw.FormatDataResponse* AsFFI()
|
||||
{
|
||||
return _inner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the underlying object immediately.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Raw.FormatDataResponse.Destroy(_inner);
|
||||
_inner = null;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
~FormatDataResponse()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,6 @@ public enum IronRdpErrorKind
|
||||
IO = 4,
|
||||
AccessDenied = 5,
|
||||
IncorrectEnumType = 6,
|
||||
Clipboard = 7,
|
||||
WrongOS = 8,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// <auto-generated/> 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);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// <auto-generated/> 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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user