feat(ffi): add a client example using Avalonia (#443)

This commit is contained in:
irvingouj @ Devolutions
2024-04-24 10:09:34 -04:00
committed by GitHub
parent fefcdc42fe
commit a232b4ee0f
54 changed files with 2094 additions and 40 deletions
@@ -0,0 +1,16 @@
# Ignore directories
bin/
obj/
# Ignore files
*.user
*.userosscache
*.suo
*.userprefs
*.dll
*.exe
*.pdb
*.cache
*.vsp
*.vspx
*.sap
@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Devolutions.IronRdp.AvaloniaExample.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
@@ -0,0 +1,24 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Devolutions.IronRdp.AvaloniaExample;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<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.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" />
<ProjectReference Include="../Devolutions.IronRdp/Devolutions.IronRdp.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devolutions.IronRdp.AvaloniaExample", "Devolutions.IronRdp.AvaloniaExample.csproj", "{B374556F-70F4-4B70-90AE-6DF00C532240}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B374556F-70F4-4B70-90AE-6DF00C532240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B374556F-70F4-4B70-90AE-6DF00C532240}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B374556F-70F4-4B70-90AE-6DF00C532240}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B374556F-70F4-4B70-90AE-6DF00C532240}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1DD5DE59-5AB4-4F5F-A2AC-CA4D7012F56E}
EndGlobalSection
EndGlobal
@@ -0,0 +1,104 @@
using Avalonia.Input;
using System;
using System.Collections.Generic;
public static class KeyCodeMapper
{
private static readonly Dictionary<PhysicalKey, ushort> KeyToScancodeMap = new Dictionary<PhysicalKey, ushort>
{
{PhysicalKey.Escape, 0x01},
{PhysicalKey.Digit1, 0x02},
{PhysicalKey.Digit2, 0x03},
{PhysicalKey.Digit3, 0x04},
{PhysicalKey.Digit4, 0x05},
{PhysicalKey.Digit5, 0x06},
{PhysicalKey.Digit6, 0x07},
{PhysicalKey.Digit7, 0x08},
{PhysicalKey.Digit8, 0x09},
{PhysicalKey.Digit9, 0x0A},
{PhysicalKey.Digit0, 0x0B},
{PhysicalKey.Minus, 0x0C},
{PhysicalKey.Equal, 0x0D},
{PhysicalKey.Backspace, 0x0E},
{PhysicalKey.Tab, 0x0F},
{PhysicalKey.Q, 0x10},
{PhysicalKey.W, 0x11},
{PhysicalKey.E, 0x12},
{PhysicalKey.R, 0x13},
{PhysicalKey.T, 0x14},
{PhysicalKey.Y, 0x15},
{PhysicalKey.U, 0x16},
{PhysicalKey.I, 0x17},
{PhysicalKey.O, 0x18},
{PhysicalKey.P, 0x19},
{PhysicalKey.BracketLeft, 0x1A},
{PhysicalKey.BracketRight, 0x1B},
{PhysicalKey.Enter, 0x1C},
{PhysicalKey.ControlLeft, 0x1D},
{PhysicalKey.A, 0x1E},
{PhysicalKey.S, 0x1F},
{PhysicalKey.D, 0x20},
{PhysicalKey.F, 0x21},
{PhysicalKey.G, 0x22},
{PhysicalKey.H, 0x23},
{PhysicalKey.J, 0x24},
{PhysicalKey.K, 0x25},
{PhysicalKey.L, 0x26},
{PhysicalKey.Semicolon, 0x27},
{PhysicalKey.Quote, 0x28},
{PhysicalKey.ShiftLeft, 0x2A},
{PhysicalKey.Backslash, 0x2B},
{PhysicalKey.Z, 0x2C},
{PhysicalKey.X, 0x2D},
{PhysicalKey.C, 0x2E},
{PhysicalKey.V, 0x2F},
{PhysicalKey.B, 0x30},
{PhysicalKey.N, 0x31},
{PhysicalKey.M, 0x32},
{PhysicalKey.Comma, 0x33},
{PhysicalKey.Period, 0x34},
{PhysicalKey.Slash, 0x35},
{PhysicalKey.ShiftRight, 0x36},
{PhysicalKey.PrintScreen, 0x37},
{PhysicalKey.AltLeft, 0x38},
{PhysicalKey.Space, 0x39},
{PhysicalKey.CapsLock, 0x3A},
{PhysicalKey.F1, 0x3B},
{PhysicalKey.F2, 0x3C},
{PhysicalKey.F3, 0x3D},
{PhysicalKey.F4, 0x3E},
{PhysicalKey.F5, 0x3F},
{PhysicalKey.F6, 0x40},
{PhysicalKey.F7, 0x41},
{PhysicalKey.F8, 0x42},
{PhysicalKey.F9, 0x43},
{PhysicalKey.F10, 0x44},
{PhysicalKey.NumLock, 0x45},
{PhysicalKey.ScrollLock, 0x46},
{PhysicalKey.Home, 0x47},
{PhysicalKey.ArrowUp, 0x48},
{PhysicalKey.PageUp, 0x49},
{PhysicalKey.NumPadSubtract, 0x4A},
{PhysicalKey.ArrowLeft, 0x4B},
{PhysicalKey.NumPad5, 0x4C},
{PhysicalKey.ArrowRight, 0x4D},
{PhysicalKey.NumPadAdd, 0x4E},
{PhysicalKey.End, 0x4F},
{PhysicalKey.ArrowDown, 0x50},
{PhysicalKey.PageDown, 0x51},
{PhysicalKey.Insert, 0x52},
{PhysicalKey.Delete, 0x53},
{PhysicalKey.F11, 0x57},
{PhysicalKey.F12, 0x58}
};
public static ushort? GetScancode(PhysicalKey key)
{
if (KeyToScancodeMap.TryGetValue(key, out ushort scancode))
{
return scancode;
}
return null;
}
}
@@ -0,0 +1,14 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="1980" d:DesignHeight="1080"
x:Class="Devolutions.IronRdp.AvaloniaExample.MainWindow"
Title="Devolutions.IronRdp.AvaloniaExample">
<Canvas Name="MainCanvas" Width="1280" Height="800" Background="Black"
PointerPressed="Canvas_OnPointerPressed" PointerMoved="Canvas_PointerMoved" PointerReleased="Canvas_PointerReleased"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</Window>
@@ -0,0 +1,273 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using System;
using System.Diagnostics;
using System.Net.Security;
using System.Threading.Tasks;
namespace Devolutions.IronRdp.AvaloniaExample;
public partial class MainWindow : Window
{
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 OnOpened(object? sender, EventArgs e)
{
WindowState = WindowState.Maximized;
var username = Environment.GetEnvironmentVariable("IRONRDP_USERNAME");
var password = Environment.GetEnvironmentVariable("IRONRDP_PASSWORD");
var domain = Environment.GetEnvironmentVariable("IRONRDP_DOMAIN");
var server = Environment.GetEnvironmentVariable("IRONRDP_SERVER");
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");
Close();
return;
}
var width = 1280;
var height = 800;
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);
canvas.KeyDown += Canvas_KeyDown;
canvas.KeyUp += Canvas_KeyUp;
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
Exception e = t.Exception!;
Trace.TraceError("Error connecting to server: " + e.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;
ReadPduAndProcessActiveStage();
});
}
private async void WriteDecodedImageToCanvas()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
var data = decodedImage!.GetData();
var bufferSize = (int)data.GetSize();
var buffer = new byte[bufferSize];
data.Fill(buffer);
using (var bitmap = this.bitmap!.Lock())
{
unsafe
{
var bitmapSpan = new Span<byte>((void*)bitmap.Address, bufferSize);
var bufferSpan = new Span<byte>(buffer);
bufferSpan.CopyTo(bitmapSpan);
}
}
image!.InvalidateVisual();
});
}
private void ReadPduAndProcessActiveStage()
{
Task.Run(async () =>
{
var keepLooping = true;
while (keepLooping)
{
var readPduTask = await framed!.ReadPdu();
Action action = readPduTask.Item1;
byte[] payload = readPduTask.Item2;
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)
{
ConfigBuilder configBuilder = ConfigBuilder.New();
configBuilder.WithUsernameAndPassword(username, password);
configBuilder.SetDomain(domain);
configBuilder.SetDesktopSize((ushort)height, (ushort)width);
configBuilder.SetClientName("IronRdp");
configBuilder.SetClientDir("C:\\");
configBuilder.SetPerformanceFlags(PerformanceFlags.NewDefault());
return configBuilder.Build();
}
private void Canvas_OnPointerPressed(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
PointerUpdateKind mouseButton = e.GetCurrentPoint((Visual?)sender).Properties.PointerUpdateKind;
MouseButtonType buttonType = mouseButton switch
{
PointerUpdateKind.LeftButtonPressed => MouseButtonType.Left,
PointerUpdateKind.RightButtonPressed => MouseButtonType.Right,
PointerUpdateKind.MiddleButtonPressed => MouseButtonType.Middle,
PointerUpdateKind.XButton1Pressed => MouseButtonType.X1,
PointerUpdateKind.XButton2Pressed => MouseButtonType.X2,
PointerUpdateKind.LeftButtonReleased => MouseButtonType.Left,
PointerUpdateKind.MiddleButtonReleased => MouseButtonType.Middle,
PointerUpdateKind.RightButtonReleased => MouseButtonType.Right,
PointerUpdateKind.XButton1Released => MouseButtonType.X1,
PointerUpdateKind.XButton2Released => MouseButtonType.X2,
PointerUpdateKind.Other => throw new NotImplementedException(),
_ => throw new NotImplementedException(),
};
var buttonOperation = MouseButton.New(buttonType).AsOperationMouseButtonPressed();
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)
{
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 _ = HandleActiveStageOutput(output);
}
private void Canvas_PointerReleased(object sender, PointerReleasedEventArgs e)
{
PointerUpdateKind mouseButton = e.GetCurrentPoint((Visual?)sender).Properties.PointerUpdateKind;
MouseButtonType buttonType = mouseButton switch
{
PointerUpdateKind.LeftButtonPressed => MouseButtonType.Left,
PointerUpdateKind.RightButtonPressed => MouseButtonType.Right,
PointerUpdateKind.MiddleButtonPressed => MouseButtonType.Middle,
PointerUpdateKind.XButton1Pressed => MouseButtonType.X1,
PointerUpdateKind.XButton2Pressed => MouseButtonType.X2,
PointerUpdateKind.LeftButtonReleased => MouseButtonType.Left,
PointerUpdateKind.MiddleButtonReleased => MouseButtonType.Middle,
PointerUpdateKind.RightButtonReleased => MouseButtonType.Right,
PointerUpdateKind.XButton1Released => MouseButtonType.X1,
PointerUpdateKind.XButton2Released => MouseButtonType.X2,
PointerUpdateKind.Other => throw new NotImplementedException(),
_ => throw new NotImplementedException(),
};
var buttonOperation = MouseButton.New(buttonType).AsOperationMouseButtonReleased();
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)
{
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 _ = HandleActiveStageOutput(output);
}
private void Canvas_KeyUp(object? sender, KeyEventArgs? e)
{
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 _ = HandleActiveStageOutput(output);
}
private async Task<bool> HandleActiveStageOutput(ActiveStageOutputIterator outputIterator)
{
try
{
while (!outputIterator.IsEmpty())
{
var output = outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false
if (output.GetEnumType() == ActiveStageOutputType.Terminate)
{
return false;
}
else if (output.GetEnumType() == ActiveStageOutputType.ResponseFrame)
{
// render the decoded image to canvas
WriteDecodedImageToCanvas();
// Send the response frame to the server
var responseFrame = output.GetResponseFrame()!;
byte[] responseFrameBytes = new byte[responseFrame.GetSize()];
responseFrame.Fill(responseFrameBytes);
await framed!.Write(responseFrameBytes);
}
else if (output.GetEnumType() == ActiveStageOutputType.GraphicsUpdate)
{
WriteDecodedImageToCanvas();
}
else if (output.GetEnumType() == ActiveStageOutputType.PointerPosition)
{
WriteDecodedImageToCanvas();
}
else if (output.GetEnumType() == ActiveStageOutputType.PointerBitmap)
{
WriteDecodedImageToCanvas();
}
}
return true;
}
catch (Exception e)
{
return false;
}
}
}
@@ -0,0 +1,38 @@
using Avalonia;
using System;
using System.Diagnostics;
namespace Devolutions.IronRdp.AvaloniaExample;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args)
{
InitializeLogging();
try{
BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
}catch(Exception e){
Trace.TraceError(e.Message);
}
}
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
public static void InitializeLogging()
{
Trace.AutoFlush = true;
TextWriterTraceListener myListener = new TextWriterTraceListener(System.IO.File.CreateText("AvaloniaExample.log"));
Trace.Listeners.Add(myListener);
}
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Devolutions.IronRdp.AvaloniaExample.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>
@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devolutions.IronRdp.ConnectExample", "Devolutions.IronRdp.ConnectExample.csproj", "{EB812E03-EDBE-4576-A84A-A26982D46BE1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EB812E03-EDBE-4576-A84A-A26982D46BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB812E03-EDBE-4576-A84A-A26982D46BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB812E03-EDBE-4576-A84A-A26982D46BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB812E03-EDBE-4576-A84A-A26982D46BE1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C47D6FCB-C82B-4C07-AA17-839B0B0CF0D2}
EndGlobalSection
EndGlobal
@@ -47,13 +47,13 @@ namespace Devolutions.IronRdp.ConnectExample
{
var output = outputIterator.Next()!; // outputIterator.Next() is not null since outputIterator.IsEmpty() is false
Console.WriteLine($"Output type: {output.GetType()}");
if (output.GetType() == ActiveStageOutputType.Terminate)
if (output.GetEnumType() == ActiveStageOutputType.Terminate)
{
Console.WriteLine("Connection terminated.");
keepLooping = false;
}
if (output.GetType() == ActiveStageOutputType.ResponseFrame)
if (output.GetEnumType() == ActiveStageOutputType.ResponseFrame)
{
var responseFrame = output.GetResponseFrame()!;
byte[] responseFrameBytes = new byte[responseFrame.GetSize()];
@@ -91,6 +91,40 @@ public partial class ActiveStage: IDisposable
}
}
/// <exception cref="IronRdpException"></exception>
/// <returns>
/// A <c>ActiveStageOutputIterator</c> allocated on Rust side.
/// </returns>
public ActiveStageOutputIterator ProcessFastpathInput(DecodedImage image, FastPathInputEventIterator fastpathInput)
{
unsafe
{
if (_inner == null)
{
throw new ObjectDisposedException("ActiveStage");
}
Raw.DecodedImage* imageRaw;
imageRaw = image.AsFFI();
if (imageRaw == null)
{
throw new ObjectDisposedException("DecodedImage");
}
Raw.FastPathInputEventIterator* fastpathInputRaw;
fastpathInputRaw = fastpathInput.AsFFI();
if (fastpathInputRaw == null)
{
throw new ObjectDisposedException("FastPathInputEventIterator");
}
Raw.SessionFfiResultBoxActiveStageOutputIteratorBoxIronRdpError result = Raw.ActiveStage.ProcessFastpathInput(_inner, imageRaw, fastpathInputRaw);
if (!result.isOk)
{
throw new IronRdpException(new IronRdpError(result.Err));
}
Raw.ActiveStageOutputIterator* retVal = result.Ok;
return new ActiveStageOutputIterator(retVal);
}
}
/// <summary>
/// Returns the underlying raw handle.
/// </summary>
@@ -23,6 +23,14 @@ public partial class ActiveStageOutput: IDisposable
}
}
public ActiveStageOutputType EnumType
{
get
{
return GetEnumType();
}
}
public InclusiveRectangle GraphicsUpdate
{
get
@@ -63,14 +71,6 @@ public partial class ActiveStageOutput: IDisposable
}
}
public ActiveStageOutputType Type
{
get
{
return GetType();
}
}
/// <summary>
/// Creates a managed <c>ActiveStageOutput</c> from a raw handle.
/// </summary>
@@ -88,7 +88,7 @@ public partial class ActiveStageOutput: IDisposable
/// <returns>
/// A <c>ActiveStageOutputType</c> allocated on C# side.
/// </returns>
public ActiveStageOutputType GetType()
public ActiveStageOutputType GetEnumType()
{
unsafe
{
@@ -96,7 +96,7 @@ public partial class ActiveStageOutput: IDisposable
{
throw new ObjectDisposedException("ActiveStageOutput");
}
Raw.ActiveStageOutputType retVal = Raw.ActiveStageOutput.GetType(_inner);
Raw.ActiveStageOutputType retVal = Raw.ActiveStageOutput.GetEnumType(_inner);
return (ActiveStageOutputType)retVal;
}
}
@@ -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 BitmapConfig: IDisposable
{
private unsafe Raw.BitmapConfig* _inner;
/// <summary>
/// Creates a managed <c>BitmapConfig</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 BitmapConfig(Raw.BitmapConfig* handle)
{
_inner = handle;
}
/// <summary>
/// Returns the underlying raw handle.
/// </summary>
public unsafe Raw.BitmapConfig* AsFFI()
{
return _inner;
}
/// <summary>
/// Destroys the underlying object immediately.
/// </summary>
public void Dispose()
{
unsafe
{
if (_inner == null)
{
return;
}
Raw.BitmapConfig.Destroy(_inner);
_inner = null;
GC.SuppressFinalize(this);
}
}
~BitmapConfig()
{
Dispose();
}
}
@@ -0,0 +1,113 @@
// <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 Char: IDisposable
{
private unsafe Raw.Char* _inner;
/// <summary>
/// Creates a managed <c>Char</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 Char(Raw.Char* handle)
{
_inner = handle;
}
/// <exception cref="IronRdpException"></exception>
/// <returns>
/// A <c>Char</c> allocated on Rust side.
/// </returns>
public static Char New(uint c)
{
unsafe
{
Raw.InputFfiResultBoxCharBoxIronRdpError result = Raw.Char.New(c);
if (!result.isOk)
{
throw new IronRdpException(new IronRdpError(result.Err));
}
Raw.Char* retVal = result.Ok;
return new Char(retVal);
}
}
/// <returns>
/// A <c>Operation</c> allocated on Rust side.
/// </returns>
public Operation AsOperationUnicodeKeyPressed()
{
unsafe
{
if (_inner == null)
{
throw new ObjectDisposedException("Char");
}
Raw.Operation* retVal = Raw.Char.AsOperationUnicodeKeyPressed(_inner);
return new Operation(retVal);
}
}
/// <returns>
/// A <c>Operation</c> allocated on Rust side.
/// </returns>
public Operation AsOperationUnicodeKeyReleased()
{
unsafe
{
if (_inner == null)
{
throw new ObjectDisposedException("Char");
}
Raw.Operation* retVal = Raw.Char.AsOperationUnicodeKeyReleased(_inner);
return new Operation(retVal);
}
}
/// <summary>
/// Returns the underlying raw handle.
/// </summary>
public unsafe Raw.Char* AsFFI()
{
return _inner;
}
/// <summary>
/// Destroys the underlying object immediately.
/// </summary>
public void Dispose()
{
unsafe
{
if (_inner == null)
{
return;
}
Raw.Char.Destroy(_inner);
_inner = null;
GC.SuppressFinalize(this);
}
}
~Char()
{
Dispose();
}
}
@@ -39,6 +39,14 @@ public partial class ClientConnectorState: IDisposable
}
}
public ConnectionActivationSequence ConnectionFinalizationResult
{
get
{
return GetConnectionFinalizationResult();
}
}
public SecurityProtocol ConnectionInitiationWaitConfirmRequestedProtocol
{
get
@@ -63,11 +71,11 @@ public partial class ClientConnectorState: IDisposable
}
}
public ClientConnectorStateType Type
public ClientConnectorStateType EnumType
{
get
{
return GetType();
return GetEnumType();
}
}
@@ -89,7 +97,7 @@ public partial class ClientConnectorState: IDisposable
/// <returns>
/// A <c>ClientConnectorStateType</c> allocated on C# side.
/// </returns>
public ClientConnectorStateType GetType()
public ClientConnectorStateType GetEnumType()
{
unsafe
{
@@ -97,7 +105,7 @@ public partial class ClientConnectorState: IDisposable
{
throw new ObjectDisposedException("ClientConnectorState");
}
Raw.ConnectorStateFfiResultClientConnectorStateTypeBoxIronRdpError result = Raw.ClientConnectorState.GetType(_inner);
Raw.ConnectorStateFfiResultClientConnectorStateTypeBoxIronRdpError result = Raw.ClientConnectorState.GetEnumType(_inner);
if (!result.isOk)
{
throw new IronRdpException(new IronRdpError(result.Err));
@@ -239,6 +247,28 @@ public partial class ClientConnectorState: IDisposable
}
}
/// <exception cref="IronRdpException"></exception>
/// <returns>
/// A <c>ConnectionActivationSequence</c> allocated on Rust side.
/// </returns>
public ConnectionActivationSequence GetConnectionFinalizationResult()
{
unsafe
{
if (_inner == null)
{
throw new ObjectDisposedException("ClientConnectorState");
}
Raw.ConnectorStateFfiResultBoxConnectionActivationSequenceBoxIronRdpError result = Raw.ClientConnectorState.GetConnectionFinalizationResult(_inner);
if (!result.isOk)
{
throw new IronRdpException(new IronRdpError(result.Err));
}
Raw.ConnectionActivationSequence* retVal = result.Ok;
return new ConnectionActivationSequence(retVal);
}
}
/// <summary>
/// Returns the underlying raw handle.
/// </summary>
@@ -23,6 +23,14 @@ public partial class ConfigBuilder: IDisposable
}
}
public BitmapConfig BitmapConfig
{
set
{
SetBitmapConfig(value);
}
}
public uint ClientBuild
{
set
@@ -326,6 +334,24 @@ public partial class ConfigBuilder: IDisposable
}
}
public void SetBitmapConfig(BitmapConfig bitmap)
{
unsafe
{
if (_inner == null)
{
throw new ObjectDisposedException("ConfigBuilder");
}
Raw.BitmapConfig* bitmapRaw;
bitmapRaw = bitmap.AsFFI();
if (bitmapRaw == null)
{
throw new ObjectDisposedException("BitmapConfig");
}
Raw.ConfigBuilder.SetBitmapConfig(_inner, bitmapRaw);
}
}
public void SetClientBuild(uint clientBuild)
{
unsafe
@@ -46,11 +46,11 @@ public struct DiplomatWriteable : IDisposable
IntPtr flushFuncPtr = Marshal.GetFunctionPointerForDelegate(flushFunc);
IntPtr growFuncPtr = Marshal.GetFunctionPointerForDelegate(growFunc);
// flushFunc and growFunc are managed objects and might be disposed of by the garbage collector.
// To prevent this, we make the context hold the references and protect the context itself
// for automatic disposal by moving it behind a GCHandle.
DiplomatWriteableContext ctx = new DiplomatWriteableContext();
DiplomatWriteableContext ctx = new DiplomatWriteableContext();
ctx.flushFunc = flushFunc;
ctx.growFunc = growFunc;
GCHandle ctxHandle = GCHandle.Alloc(ctx);
@@ -81,7 +81,7 @@ public struct DiplomatWriteable : IDisposable
{
throw new IndexOutOfRangeException("DiplomatWriteable buffer is too big");
}
return Marshal.PtrToStringUTF8(buf, (int)len);
return Marshal.PtrToStringUTF8(buf, (int) len);
#else
byte[] utf8 = ToUtf8Bytes();
return DiplomatUtils.Utf8ToString(utf8);
@@ -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 FastPathInputEvent: IDisposable
{
private unsafe Raw.FastPathInputEvent* _inner;
/// <summary>
/// Creates a managed <c>FastPathInputEvent</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 FastPathInputEvent(Raw.FastPathInputEvent* handle)
{
_inner = handle;
}
/// <summary>
/// Returns the underlying raw handle.
/// </summary>
public unsafe Raw.FastPathInputEvent* AsFFI()
{
return _inner;
}
/// <summary>
/// Destroys the underlying object immediately.
/// </summary>
public void Dispose()
{
unsafe
{
if (_inner == null)
{
return;
}
Raw.FastPathInputEvent.Destroy(_inner);
_inner = null;
GC.SuppressFinalize(this);
}
}
~FastPathInputEvent()
{
Dispose();
}
}

Some files were not shown because too many files have changed in this diff Show More