diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/.gitignore b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/.gitignore
new file mode 100644
index 000000000..a374dd6be
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/.gitignore
@@ -0,0 +1,16 @@
+# Ignore directories
+bin/
+obj/
+
+# Ignore files
+*.user
+*.userosscache
+*.suo
+*.userprefs
+*.dll
+*.exe
+*.pdb
+*.cache
+*.vsp
+*.vspx
+*.sap
diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml
new file mode 100644
index 000000000..f807f50e9
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml.cs b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml.cs
new file mode 100644
index 000000000..23f0f0dc1
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/App.axaml.cs
@@ -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();
+ }
+}
\ 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
new file mode 100644
index 000000000..d358f1e4c
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.csproj
@@ -0,0 +1,21 @@
+
+
+ WinExe
+ net8.0
+ enable
+ true
+ app.manifest
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.sln b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.sln
new file mode 100644
index 000000000..dc1a353ba
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/Devolutions.IronRdp.AvaloniaExample.sln
@@ -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
diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/KeyCodeMapper.cs b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/KeyCodeMapper.cs
new file mode 100644
index 000000000..b94824579
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/KeyCodeMapper.cs
@@ -0,0 +1,104 @@
+using Avalonia.Input;
+using System;
+using System.Collections.Generic;
+
+public static class KeyCodeMapper
+{
+ private static readonly Dictionary KeyToScancodeMap = new Dictionary
+ {
+ {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;
+ }
+}
diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml
new file mode 100644
index 000000000..71a325e2c
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml
@@ -0,0 +1,14 @@
+
+
+
+
\ 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
new file mode 100644
index 000000000..75720d1e0
--- /dev/null
+++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs
@@ -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? 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