Browse Source

Merge pull request #2011 from kekekeks/x11

X11 backend
pull/2248/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
0b3a3e1668
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      Avalonia.sln
  2. 8
      azure-pipelines.yml
  3. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  4. 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  5. 10
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  6. 6
      samples/PlatformSanityChecks/App.xaml
  7. 13
      samples/PlatformSanityChecks/App.xaml.cs
  8. 13
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  9. 132
      samples/PlatformSanityChecks/Program.cs
  10. 6
      src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs
  11. 4
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  12. 4
      src/Avalonia.Input/MouseDevice.cs
  13. 2
      src/Avalonia.Input/Raw/RawDragEvent.cs
  14. 4
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  15. 2
      src/Avalonia.Input/Raw/RawKeyEventArgs.cs
  16. 2
      src/Avalonia.Input/Raw/RawMouseEventArgs.cs
  17. 2
      src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
  18. 2
      src/Avalonia.Input/Raw/RawTextInputEventArgs.cs
  19. 56
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  20. 12
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  21. 6
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  22. 14
      src/Avalonia.X11/Avalonia.X11.csproj
  23. 2108
      src/Avalonia.X11/Keysyms.cs
  24. 17
      src/Avalonia.X11/Stubs.cs
  25. 207
      src/Avalonia.X11/X11Atoms.cs
  26. 234
      src/Avalonia.X11/X11Clipboard.cs
  27. 57
      src/Avalonia.X11/X11CursorFactory.cs
  28. 110
      src/Avalonia.X11/X11Enums.cs
  29. 12
      src/Avalonia.X11/X11Exception.cs
  30. 59
      src/Avalonia.X11/X11Framebuffer.cs
  31. 29
      src/Avalonia.X11/X11FramebufferSurface.cs
  32. 105
      src/Avalonia.X11/X11IconLoader.cs
  33. 85
      src/Avalonia.X11/X11Info.cs
  34. 232
      src/Avalonia.X11/X11KeyTransform.cs
  35. 93
      src/Avalonia.X11/X11Platform.cs
  36. 285
      src/Avalonia.X11/X11PlatformThreading.cs
  37. 252
      src/Avalonia.X11/X11Screens.cs
  38. 1886
      src/Avalonia.X11/X11Structs.cs
  39. 873
      src/Avalonia.X11/X11Window.cs
  40. 30
      src/Avalonia.X11/XError.cs
  41. 269
      src/Avalonia.X11/XI2Manager.cs
  42. 283
      src/Avalonia.X11/XIStructs.cs
  43. 632
      src/Avalonia.X11/XLib.cs
  44. 115
      src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs
  45. 13
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  46. 36
      src/Gtk/Avalonia.Gtk3/SystemDialogs.cs
  47. 29
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  48. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  49. 8
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  50. 56
      src/Skia/Avalonia.Skia/GlRenderTarget.cs
  51. 3
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  52. 10
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

54
Avalonia.sln

@ -196,6 +196,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1767,6 +1771,54 @@ Global
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhone.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhone.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.Build.0 = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhone.ActiveCfg = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhone.Build.0 = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{41B02319-965D-4945-8005-C1A3D1224165}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhone.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhone.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|Any CPU.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1821,6 +1873,8 @@ Global
{D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

8
azure-pipelines.yml

@ -92,13 +92,13 @@ jobs:
inputs:
pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/'
artifactName: 'Avalonia.Native.OSX'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
condition: succeeded()
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
artifactName: 'NuGetOSX'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
condition: succeeded()
- job: Windows
pool:
@ -127,10 +127,10 @@ jobs:
inputs:
pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
artifactName: 'NuGet'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
condition: succeeded()
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
artifactName: 'Samples'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
condition: succeeded()

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -9,6 +9,7 @@
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup>

1
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -6,6 +6,7 @@
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<Button Name="DecoratedWindow">Decorated window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button>
</StackPanel>
</UserControl>

10
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -31,9 +31,13 @@ namespace ControlCatalog.Pages
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().ShowDialog(GetWindow());
};
{
new DecoratedWindow().Show();
};
this.FindControl<Button>("DecoratedWindowDialog").Click += delegate
{
new DecoratedWindow().ShowDialog(GetWindow());
};
this.FindControl<Button>("Dialog").Click += delegate
{
new MainWindow().ShowDialog(GetWindow());

6
samples/PlatformSanityChecks/App.xaml

@ -0,0 +1,6 @@
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
</Application.Styles>
</Application>

13
samples/PlatformSanityChecks/App.xaml.cs

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Markup.Xaml;
namespace PlatformSanityChecks
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
}
}

13
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
</ItemGroup>
</Project>

132
samples/PlatformSanityChecks/Program.cs

@ -0,0 +1,132 @@
using System;
using System.Diagnostics;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.X11;
namespace PlatformSanityChecks
{
public class Program
{
static Thread UiThread;
static void Main(string[] args)
{
UiThread = Thread.CurrentThread;
AppBuilder.Configure<App>().RuntimePlatformServicesInitializer();
var app = new App();
AvaloniaX11PlatformExtensions.InitializeX11Platform();
CheckPlatformThreading();
}
static bool CheckAccess() => UiThread == Thread.CurrentThread;
static void VerifyAccess()
{
if (!CheckAccess())
Die("Call from invalid thread");
}
static Exception Die(string error)
{
Console.Error.WriteLine(error);
Console.Error.WriteLine(Environment.StackTrace);
Process.GetCurrentProcess().Kill();
throw new Exception(error);
}
static IDisposable Enter([CallerMemberName] string caller = null)
{
Console.WriteLine("Entering " + caller);
return Disposable.Create(() => { Console.WriteLine("Leaving " + caller); });
}
static void EnterLoop(Action<CancellationTokenSource> cb, [CallerMemberName] string caller = null)
{
using (Enter(caller))
{
var cts = new CancellationTokenSource();
cb(cts);
Dispatcher.UIThread.MainLoop(cts.Token);
if (!cts.IsCancellationRequested)
Die("Unexpected loop exit");
}
}
static void CheckTimerOrdering() => EnterLoop(cts =>
{
bool firstFired = false, secondFired = false;
DispatcherTimer.Run(() =>
{
Console.WriteLine("Second tick");
VerifyAccess();
if (!firstFired)
throw Die("Invalid timer ordering");
if (secondFired)
throw Die("Invocation of finished timer");
secondFired = true;
cts.Cancel();
return false;
}, TimeSpan.FromSeconds(2));
DispatcherTimer.Run(() =>
{
Console.WriteLine("First tick");
VerifyAccess();
if (secondFired)
throw Die("Invalid timer ordering");
if (firstFired)
throw Die("Invocation of finished timer");
firstFired = true;
return false;
}, TimeSpan.FromSeconds(1));
});
static void CheckTimerTicking() => EnterLoop(cts =>
{
int ticks = 0;
var st = Stopwatch.StartNew();
DispatcherTimer.Run(() =>
{
ticks++;
Console.WriteLine($"Tick {ticks} at {st.Elapsed}");
if (ticks == 5)
{
if (st.Elapsed.TotalSeconds < 4.5)
Die("Timer is too fast");
if (st.Elapsed.TotalSeconds > 6)
Die("Timer is too slow");
cts.Cancel();
return false;
}
return true;
}, TimeSpan.FromSeconds(1));
});
static void CheckSignaling() => EnterLoop(cts =>
{
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(100);
Dispatcher.UIThread.Post(() =>
{
VerifyAccess();
cts.Cancel();
});
});
});
static void CheckPlatformThreading()
{
CheckSignaling();
CheckTimerOrdering();
CheckTimerTicking();
}
}
}

6
src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs

@ -29,7 +29,7 @@ namespace Avalonia
}
else
{
LoadGtk3(builder);
LoadX11(builder);
LoadSkia(builder);
}
return builder;
@ -42,9 +42,9 @@ namespace Avalonia
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseWin32();
static void LoadGtk3<TAppBuilder>(TAppBuilder builder)
static void LoadX11<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
=> builder.UseGtk3();
=> builder.UseX11();
static void LoadDirect2D1<TAppBuilder>(TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()

4
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" />
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" />
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" />
<ProjectReference Include="../../src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj" />
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" />
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" />
<ProjectReference Include="../Avalonia.X11/Avalonia.X11.csproj" />
</ItemGroup>
</Project>

4
src/Avalonia.Input/MouseDevice.cs

@ -18,7 +18,7 @@ namespace Avalonia.Input
{
private int _clickCount;
private Rect _lastClickRect;
private uint _lastClickTime;
private ulong _lastClickTime;
private IInputElement _captured;
private IDisposable _capturedSubscription;
@ -152,7 +152,7 @@ namespace Avalonia.Input
ClearPointerOver(this, root);
}
private bool MouseDown(IMouseDevice device, uint timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);

2
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -3,7 +3,7 @@
public class RawDragEvent : RawInputEventArgs
{
public IInputElement InputRoot { get; }
public Point Location { get; }
public Point Location { get; set; }
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }

4
src/Avalonia.Input/Raw/RawInputEventArgs.cs

@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw
/// </summary>
/// <param name="device">The associated device.</param>
/// <param name="timestamp">The event timestamp.</param>
public RawInputEventArgs(IInputDevice device, uint timestamp)
public RawInputEventArgs(IInputDevice device, ulong timestamp)
{
Contract.Requires<ArgumentNullException>(device != null);
@ -47,6 +47,6 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the timestamp associated with the event.
/// </summary>
public uint Timestamp { get; private set; }
public ulong Timestamp { get; private set; }
}
}

2
src/Avalonia.Input/Raw/RawKeyEventArgs.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input.Raw
{
public RawKeyEventArgs(
IKeyboardDevice device,
uint timestamp,
ulong timestamp,
RawKeyEventType type,
Key key, InputModifiers modifiers)
: base(device, timestamp)

2
src/Avalonia.Input/Raw/RawMouseEventArgs.cs

@ -35,7 +35,7 @@ namespace Avalonia.Input.Raw
/// <param name="inputModifiers">The input modifiers.</param>
public RawMouseEventArgs(
IInputDevice device,
uint timestamp,
ulong timestamp,
IInputRoot root,
RawMouseEventType type,
Point position,

2
src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs

@ -8,7 +8,7 @@ namespace Avalonia.Input.Raw
{
public RawMouseWheelEventArgs(
IInputDevice device,
uint timestamp,
ulong timestamp,
IInputRoot root,
Point position,
Vector delta, InputModifiers inputModifiers)

2
src/Avalonia.Input/Raw/RawTextInputEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input.Raw
{
public string Text { get; set; }
public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp)
public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp)
{
Text = text;
}

56
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -128,9 +128,25 @@ namespace Avalonia.Rendering
}
Stop();
DisposeRenderTarget();
}
void DisposeRenderTarget()
{
using (var l = _lock.TryLock())
{
if(l == null)
{
// We are still trying to render on the render thread, try again a bit later
DispatcherTimer.RunOnce(DisposeRenderTarget, TimeSpan.FromMilliseconds(50),
DispatcherPriority.Background);
return;
}
Layers.Clear();
RenderTarget?.Dispose();
Layers.Clear();
RenderTarget?.Dispose();
RenderTarget = null;
}
}
/// <inheritdoc/>
@ -152,7 +168,8 @@ namespace Avalonia.Rendering
var t = (IRenderLoopTask)this;
if(t.NeedsUpdate)
UpdateScene();
Render(true);
if(_scene.Item != null)
Render(true);
}
/// <inheritdoc/>
@ -336,16 +353,34 @@ namespace Avalonia.Rendering
private void RenderToLayers(Scene scene)
{
if (scene.Layers.HasDirty)
foreach (var layer in scene.Layers)
{
foreach (var layer in scene.Layers)
{
var renderTarget = Layers[layer.LayerRoot].Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
var renderLayer = Layers[layer.LayerRoot];
if (layer.Dirty.IsEmpty && !renderLayer.IsEmpty)
continue;
var renderTarget = renderLayer.Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node != null)
if (node != null)
{
using (var context = renderTarget.Item.CreateDrawingContext(this))
{
using (var context = renderTarget.Item.CreateDrawingContext(this))
if (renderLayer.IsEmpty)
{
// Render entire layer root node
context.Clear(Colors.Transparent);
context.Transform = Matrix.Identity;
context.PushClip(node.ClipBounds);
Render(context, node, layer.LayerRoot, node.ClipBounds);
context.PopClip();
if (DrawDirtyRects)
{
_dirtyRectsDisplay.Add(node.ClipBounds);
}
renderLayer.IsEmpty = false;
}
else
{
foreach (var rect in layer.Dirty)
{
@ -364,6 +399,7 @@ namespace Avalonia.Rendering
}
}
}
}
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)

12
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -7,39 +7,39 @@ namespace Avalonia.Rendering
{
public class RenderLayer
{
private readonly IDrawingContextImpl _drawingContext;
public RenderLayer(
IDrawingContextImpl drawingContext,
Size size,
double scaling,
IVisual layerRoot)
{
_drawingContext = drawingContext;
Bitmap = RefCountable.Create(drawingContext.CreateLayer(size));
Size = size;
Scaling = scaling;
LayerRoot = layerRoot;
IsEmpty = true;
}
public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; }
public bool IsEmpty { get; set; }
public double Scaling { get; private set; }
public Size Size { get; private set; }
public IVisual LayerRoot { get; }
public void ResizeBitmap(Size size, double scaling)
public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling)
{
if (Size != size || Scaling != scaling)
{
var resized = RefCountable.Create(_drawingContext.CreateLayer(size));
var resized = RefCountable.Create(drawingContext.CreateLayer(size));
using (var context = resized.Item.CreateDrawingContext(null))
{
context.Clear(Colors.Transparent);
context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size));
Bitmap.Dispose();
Bitmap = resized;
Scaling = scaling;
Size = size;
IsEmpty = true;
}
}
}

6
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -29,11 +29,11 @@ namespace Avalonia.Rendering
}
else
{
layer.ResizeBitmap(scene.Size, scene.Scaling);
layer.RecreateBitmap(context, scene.Size, scene.Scaling);
}
}
for (var i = _inner.Count - 1; i >= 0; --i)
for (var i = 0; i < _inner.Count;)
{
var layer = _inner[i];
@ -43,6 +43,8 @@ namespace Avalonia.Rendering
_inner.RemoveAt(i);
_index.Remove(layer.LayerRoot);
}
else
i++;
}
}

14
src/Avalonia.X11/Avalonia.X11.csproj

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
</Project>

2108
src/Avalonia.X11/Keysyms.cs

File diff suppressed because it is too large

17
src/Avalonia.X11/Stubs.cs

@ -0,0 +1,17 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.X11
{
class PlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
}
}

207
src/Avalonia.X11/X11Atoms.cs

@ -0,0 +1,207 @@
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
//
//
using System;
using System.Linq;
using static Avalonia.X11.XLib;
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable IdentifierTypo
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable CommentTypo
// ReSharper disable ArrangeThisQualifier
// ReSharper disable NotAccessedField.Global
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
#pragma warning disable 649
namespace Avalonia.X11
{
internal class X11Atoms
{
// Our atoms
public readonly IntPtr AnyPropertyType = (IntPtr)0;
public readonly IntPtr XA_PRIMARY = (IntPtr)1;
public readonly IntPtr XA_SECONDARY = (IntPtr)2;
public readonly IntPtr XA_ARC = (IntPtr)3;
public readonly IntPtr XA_ATOM = (IntPtr)4;
public readonly IntPtr XA_BITMAP = (IntPtr)5;
public readonly IntPtr XA_CARDINAL = (IntPtr)6;
public readonly IntPtr XA_COLORMAP = (IntPtr)7;
public readonly IntPtr XA_CURSOR = (IntPtr)8;
public readonly IntPtr XA_CUT_BUFFER0 = (IntPtr)9;
public readonly IntPtr XA_CUT_BUFFER1 = (IntPtr)10;
public readonly IntPtr XA_CUT_BUFFER2 = (IntPtr)11;
public readonly IntPtr XA_CUT_BUFFER3 = (IntPtr)12;
public readonly IntPtr XA_CUT_BUFFER4 = (IntPtr)13;
public readonly IntPtr XA_CUT_BUFFER5 = (IntPtr)14;
public readonly IntPtr XA_CUT_BUFFER6 = (IntPtr)15;
public readonly IntPtr XA_CUT_BUFFER7 = (IntPtr)16;
public readonly IntPtr XA_DRAWABLE = (IntPtr)17;
public readonly IntPtr XA_FONT = (IntPtr)18;
public readonly IntPtr XA_INTEGER = (IntPtr)19;
public readonly IntPtr XA_PIXMAP = (IntPtr)20;
public readonly IntPtr XA_POINT = (IntPtr)21;
public readonly IntPtr XA_RECTANGLE = (IntPtr)22;
public readonly IntPtr XA_RESOURCE_MANAGER = (IntPtr)23;
public readonly IntPtr XA_RGB_COLOR_MAP = (IntPtr)24;
public readonly IntPtr XA_RGB_BEST_MAP = (IntPtr)25;
public readonly IntPtr XA_RGB_BLUE_MAP = (IntPtr)26;
public readonly IntPtr XA_RGB_DEFAULT_MAP = (IntPtr)27;
public readonly IntPtr XA_RGB_GRAY_MAP = (IntPtr)28;
public readonly IntPtr XA_RGB_GREEN_MAP = (IntPtr)29;
public readonly IntPtr XA_RGB_RED_MAP = (IntPtr)30;
public readonly IntPtr XA_STRING = (IntPtr)31;
public readonly IntPtr XA_VISUALID = (IntPtr)32;
public readonly IntPtr XA_WINDOW = (IntPtr)33;
public readonly IntPtr XA_WM_COMMAND = (IntPtr)34;
public readonly IntPtr XA_WM_HINTS = (IntPtr)35;
public readonly IntPtr XA_WM_CLIENT_MACHINE = (IntPtr)36;
public readonly IntPtr XA_WM_ICON_NAME = (IntPtr)37;
public readonly IntPtr XA_WM_ICON_SIZE = (IntPtr)38;
public readonly IntPtr XA_WM_NAME = (IntPtr)39;
public readonly IntPtr XA_WM_NORMAL_HINTS = (IntPtr)40;
public readonly IntPtr XA_WM_SIZE_HINTS = (IntPtr)41;
public readonly IntPtr XA_WM_ZOOM_HINTS = (IntPtr)42;
public readonly IntPtr XA_MIN_SPACE = (IntPtr)43;
public readonly IntPtr XA_NORM_SPACE = (IntPtr)44;
public readonly IntPtr XA_MAX_SPACE = (IntPtr)45;
public readonly IntPtr XA_END_SPACE = (IntPtr)46;
public readonly IntPtr XA_SUPERSCRIPT_X = (IntPtr)47;
public readonly IntPtr XA_SUPERSCRIPT_Y = (IntPtr)48;
public readonly IntPtr XA_SUBSCRIPT_X = (IntPtr)49;
public readonly IntPtr XA_SUBSCRIPT_Y = (IntPtr)50;
public readonly IntPtr XA_UNDERLINE_POSITION = (IntPtr)51;
public readonly IntPtr XA_UNDERLINE_THICKNESS = (IntPtr)52;
public readonly IntPtr XA_STRIKEOUT_ASCENT = (IntPtr)53;
public readonly IntPtr XA_STRIKEOUT_DESCENT = (IntPtr)54;
public readonly IntPtr XA_ITALIC_ANGLE = (IntPtr)55;
public readonly IntPtr XA_X_HEIGHT = (IntPtr)56;
public readonly IntPtr XA_QUAD_WIDTH = (IntPtr)57;
public readonly IntPtr XA_WEIGHT = (IntPtr)58;
public readonly IntPtr XA_POINT_SIZE = (IntPtr)59;
public readonly IntPtr XA_RESOLUTION = (IntPtr)60;
public readonly IntPtr XA_COPYRIGHT = (IntPtr)61;
public readonly IntPtr XA_NOTICE = (IntPtr)62;
public readonly IntPtr XA_FONT_NAME = (IntPtr)63;
public readonly IntPtr XA_FAMILY_NAME = (IntPtr)64;
public readonly IntPtr XA_FULL_NAME = (IntPtr)65;
public readonly IntPtr XA_CAP_HEIGHT = (IntPtr)66;
public readonly IntPtr XA_WM_CLASS = (IntPtr)67;
public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68;
public readonly IntPtr WM_PROTOCOLS;
public readonly IntPtr WM_DELETE_WINDOW;
public readonly IntPtr WM_TAKE_FOCUS;
public readonly IntPtr _NET_SUPPORTED;
public readonly IntPtr _NET_CLIENT_LIST;
public readonly IntPtr _NET_NUMBER_OF_DESKTOPS;
public readonly IntPtr _NET_DESKTOP_GEOMETRY;
public readonly IntPtr _NET_DESKTOP_VIEWPORT;
public readonly IntPtr _NET_CURRENT_DESKTOP;
public readonly IntPtr _NET_DESKTOP_NAMES;
public readonly IntPtr _NET_ACTIVE_WINDOW;
public readonly IntPtr _NET_WORKAREA;
public readonly IntPtr _NET_SUPPORTING_WM_CHECK;
public readonly IntPtr _NET_VIRTUAL_ROOTS;
public readonly IntPtr _NET_DESKTOP_LAYOUT;
public readonly IntPtr _NET_SHOWING_DESKTOP;
public readonly IntPtr _NET_CLOSE_WINDOW;
public readonly IntPtr _NET_MOVERESIZE_WINDOW;
public readonly IntPtr _NET_WM_MOVERESIZE;
public readonly IntPtr _NET_RESTACK_WINDOW;
public readonly IntPtr _NET_REQUEST_FRAME_EXTENTS;
public readonly IntPtr _NET_WM_NAME;
public readonly IntPtr _NET_WM_VISIBLE_NAME;
public readonly IntPtr _NET_WM_ICON_NAME;
public readonly IntPtr _NET_WM_VISIBLE_ICON_NAME;
public readonly IntPtr _NET_WM_DESKTOP;
public readonly IntPtr _NET_WM_WINDOW_TYPE;
public readonly IntPtr _NET_WM_STATE;
public readonly IntPtr _NET_WM_ALLOWED_ACTIONS;
public readonly IntPtr _NET_WM_STRUT;
public readonly IntPtr _NET_WM_STRUT_PARTIAL;
public readonly IntPtr _NET_WM_ICON_GEOMETRY;
public readonly IntPtr _NET_WM_ICON;
public readonly IntPtr _NET_WM_PID;
public readonly IntPtr _NET_WM_HANDLED_ICONS;
public readonly IntPtr _NET_WM_USER_TIME;
public readonly IntPtr _NET_FRAME_EXTENTS;
public readonly IntPtr _NET_WM_PING;
public readonly IntPtr _NET_WM_SYNC_REQUEST;
public readonly IntPtr _NET_SYSTEM_TRAY_S;
public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION;
public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE;
public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ;
public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT;
public readonly IntPtr _XEMBED;
public readonly IntPtr _XEMBED_INFO;
public readonly IntPtr _MOTIF_WM_HINTS;
public readonly IntPtr _NET_WM_STATE_SKIP_TASKBAR;
public readonly IntPtr _NET_WM_STATE_ABOVE;
public readonly IntPtr _NET_WM_STATE_MODAL;
public readonly IntPtr _NET_WM_STATE_HIDDEN;
public readonly IntPtr _NET_WM_CONTEXT_HELP;
public readonly IntPtr _NET_WM_WINDOW_OPACITY;
public readonly IntPtr _NET_WM_WINDOW_TYPE_DESKTOP;
public readonly IntPtr _NET_WM_WINDOW_TYPE_DOCK;
public readonly IntPtr _NET_WM_WINDOW_TYPE_TOOLBAR;
public readonly IntPtr _NET_WM_WINDOW_TYPE_MENU;
public readonly IntPtr _NET_WM_WINDOW_TYPE_UTILITY;
public readonly IntPtr _NET_WM_WINDOW_TYPE_SPLASH;
public readonly IntPtr _NET_WM_WINDOW_TYPE_DIALOG;
public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL;
public readonly IntPtr CLIPBOARD;
public readonly IntPtr CLIPBOARD_MANAGER;
public readonly IntPtr SAVE_TARGETS;
public readonly IntPtr MULTIPLE;
public readonly IntPtr PRIMARY;
public readonly IntPtr OEMTEXT;
public readonly IntPtr UNICODETEXT;
public readonly IntPtr TARGETS;
public readonly IntPtr UTF8_STRING;
public readonly IntPtr UTF16_STRING;
public readonly IntPtr ATOM_PAIR;
public X11Atoms(IntPtr display)
{
// make sure this array stays in sync with the statements below
var fields = typeof(X11Atoms).GetFields()
.Where(f => f.FieldType == typeof(IntPtr) && (IntPtr)f.GetValue(this) == IntPtr.Zero).ToArray();
var atomNames = fields.Select(f => f.Name).ToArray();
IntPtr[] atoms = new IntPtr [atomNames.Length];
;
XInternAtoms(display, atomNames, atomNames.Length, true, atoms);
for (var c = 0; c < fields.Length; c++)
fields[c].SetValue(this, atoms[c]);
}
}
}

234
src/Avalonia.X11/X11Clipboard.cs

@ -0,0 +1,234 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11Clipboard : IClipboard
{
private readonly X11Info _x11;
private string _storedString;
private IntPtr _handle;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<string> _requestedTextTcs;
private readonly IntPtr[] _textAtoms;
private readonly IntPtr _avaloniaSaveTargetsAtom;
public X11Clipboard(AvaloniaX11Platform platform)
{
_x11 = platform.Info;
_handle = CreateEventWindow(platform, OnEvent);
_avaloniaSaveTargetsAtom = XInternAtom(_x11.Display, "AVALONIA_SAVE_TARGETS_PROPERTY_ATOM", false);
_textAtoms = new[]
{
_x11.Atoms.XA_STRING,
_x11.Atoms.OEMTEXT,
_x11.Atoms.UTF8_STRING,
_x11.Atoms.UTF16_STRING
}.Where(a => a != IntPtr.Zero).ToArray();
}
Encoding GetStringEncoding(IntPtr atom)
{
return (atom == _x11.Atoms.XA_STRING
|| atom == _x11.Atoms.OEMTEXT)
? Encoding.ASCII
: atom == _x11.Atoms.UTF8_STRING
? Encoding.UTF8
: atom == _x11.Atoms.UTF16_STRING
? Encoding.Unicode
: null;
}
private unsafe void OnEvent(XEvent ev)
{
if (ev.type == XEventName.SelectionRequest)
{
var sel = ev.SelectionRequestEvent;
var resp = new XEvent
{
SelectionEvent =
{
type = XEventName.SelectionNotify,
send_event = true,
display = _x11.Display,
selection = sel.selection,
target = sel.target,
requestor = sel.requestor,
time = sel.time,
property = IntPtr.Zero
}
};
if (sel.selection == _x11.Atoms.CLIPBOARD)
{
resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property);
}
XSendEvent(_x11.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp);
}
IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property)
{
Encoding textEnc;
if (target == _x11.Atoms.TARGETS)
{
var atoms = _textAtoms;
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE})
.ToArray();
XChangeProperty(_x11.Display, window, property,
target, 32, PropertyMode.Replace, atoms, atoms.Length);
return property;
}
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
return property;
}
else if ((textEnc = GetStringEncoding(target)) != null)
{
var data = textEnc.GetBytes(_storedString ?? "");
fixed (void* pdata = data)
XChangeProperty(_x11.Display, window, property, target, 8,
PropertyMode.Replace,
pdata, data.Length);
return property;
}
else if (target == _x11.Atoms.MULTIPLE && _x11.Atoms.MULTIPLE != IntPtr.Zero)
{
XGetWindowProperty(_x11.Display, window, property, IntPtr.Zero, new IntPtr(0x7fffffff), false,
_x11.Atoms.ATOM_PAIR, out _, out var actualFormat, out var nitems, out _, out var prop);
if (nitems == IntPtr.Zero)
return IntPtr.Zero;
if (actualFormat == 32)
{
var data = (IntPtr*)prop.ToPointer();
for (var c = 0; c < nitems.ToInt32(); c += 2)
{
var subTarget = data[c];
var subProp = data[c + 1];
var converted = WriteTargetToProperty(subTarget, window, subProp);
data[c + 1] = converted;
}
XChangeProperty(_x11.Display, window, property, _x11.Atoms.ATOM_PAIR, 32, PropertyMode.Replace,
prop.ToPointer(), nitems.ToInt32());
}
XFree(prop);
return property;
}
else
return IntPtr.Zero;
}
if (ev.type == XEventName.SelectionNotify && ev.SelectionEvent.selection == _x11.Atoms.CLIPBOARD)
{
var sel = ev.SelectionEvent;
if (sel.property == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null);
}
XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType,
out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop);
Encoding textEnc = null;
if (nitems == IntPtr.Zero)
{
_requestedFormatsTcs?.TrySetResult(null);
_requestedTextTcs?.TrySetResult(null);
}
else
{
if (sel.property == _x11.Atoms.TARGETS)
{
if (actualFormat != 32)
_requestedFormatsTcs?.TrySetResult(null);
else
{
var formats = new IntPtr[nitems.ToInt32()];
Marshal.Copy(prop, formats, 0, formats.Length);
_requestedFormatsTcs?.TrySetResult(formats);
}
}
else if ((textEnc = GetStringEncoding(sel.property)) != null)
{
var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32());
_requestedTextTcs?.TrySetResult(text);
}
}
XFree(prop);
}
}
Task<IntPtr[]> SendFormatRequest()
{
if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, _handle,
IntPtr.Zero);
return _requestedFormatsTcs.Task;
}
Task<string> SendTextRequest(IntPtr format)
{
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted)
_requestedTextTcs = new TaskCompletionSource<string>();
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero);
return _requestedTextTcs.Task;
}
public async Task<string> GetTextAsync()
{
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero)
return null;
var res = await SendFormatRequest();
var target = _x11.Atoms.UTF8_STRING;
if (res != null)
{
var preferredFormats = new[] {_x11.Atoms.UTF16_STRING, _x11.Atoms.UTF8_STRING, _x11.Atoms.XA_STRING};
foreach (var pf in preferredFormats)
if (res.Contains(pf))
{
target = pf;
break;
}
}
return await SendTextRequest(target);
}
void StoreAtomsInClipboardManager(IntPtr[] atoms)
{
if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER);
if (clipboardManager != IntPtr.Zero)
{
XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32,
PropertyMode.Replace,
atoms, atoms.Length);
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS,
_avaloniaSaveTargetsAtom, _handle, IntPtr.Zero);
}
}
}
public Task SetTextAsync(string text)
{
_storedString = text;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
StoreAtomsInClipboardManager(_textAtoms);
return Task.CompletedTask;
}
public Task ClearAsync()
{
return SetTextAsync(null);
}
}
}

57
src/Avalonia.X11/X11CursorFactory.cs

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
{
private readonly IntPtr _display;
private Dictionary<CursorFontShape, IntPtr> _cursors;
private static readonly Dictionary<StandardCursorType, CursorFontShape> s_mapping =
new Dictionary<StandardCursorType, CursorFontShape>
{
{StandardCursorType.Arrow, CursorFontShape.XC_top_left_arrow},
{StandardCursorType.Cross, CursorFontShape.XC_cross},
{StandardCursorType.Hand, CursorFontShape.XC_hand1},
{StandardCursorType.Help, CursorFontShape.XC_question_arrow},
{StandardCursorType.Ibeam, CursorFontShape.XC_xterm},
{StandardCursorType.No, CursorFontShape.XC_X_cursor},
{StandardCursorType.Wait, CursorFontShape.XC_watch},
{StandardCursorType.AppStarting, CursorFontShape.XC_watch},
{StandardCursorType.BottomSize, CursorFontShape.XC_bottom_side},
{StandardCursorType.DragCopy, CursorFontShape.XC_center_ptr},
{StandardCursorType.DragLink, CursorFontShape.XC_fleur},
{StandardCursorType.DragMove, CursorFontShape.XC_diamond_cross},
{StandardCursorType.LeftSide, CursorFontShape.XC_left_side},
{StandardCursorType.RightSide, CursorFontShape.XC_right_side},
{StandardCursorType.SizeAll, CursorFontShape.XC_sizing},
{StandardCursorType.TopSide, CursorFontShape.XC_top_side},
{StandardCursorType.UpArrow, CursorFontShape.XC_sb_up_arrow},
{StandardCursorType.BottomLeftCorner, CursorFontShape.XC_bottom_left_corner},
{StandardCursorType.BottomRightCorner, CursorFontShape.XC_bottom_right_corner},
{StandardCursorType.SizeNorthSouth, CursorFontShape.XC_sb_v_double_arrow},
{StandardCursorType.SizeWestEast, CursorFontShape.XC_sb_h_double_arrow},
{StandardCursorType.TopLeftCorner, CursorFontShape.XC_top_left_corner},
{StandardCursorType.TopRightCorner, CursorFontShape.XC_top_right_corner},
};
public X11CursorFactory(IntPtr display)
{
_display = display;
_cursors = Enum.GetValues(typeof(CursorFontShape)).Cast<CursorFontShape>()
.ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id));
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
var handle = s_mapping.TryGetValue(cursorType, out var shape)
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
return new PlatformHandle(handle, "XCURSOR");
}
}
}

110
src/Avalonia.X11/X11Enums.cs

@ -0,0 +1,110 @@
using System;
namespace Avalonia.X11
{
public enum Status
{
Success = 0, /* everything's okay */
BadRequest = 1, /* bad request code */
BadValue = 2, /* int parameter out of range */
BadWindow = 3, /* parameter not a Window */
BadPixmap = 4, /* parameter not a Pixmap */
BadAtom = 5, /* parameter not an Atom */
BadCursor = 6, /* parameter not a Cursor */
BadFont = 7, /* parameter not a Font */
BadMatch = 8, /* parameter mismatch */
BadDrawable = 9, /* parameter not a Pixmap or Window */
BadAccess = 10, /* depending on context:
- key/button already grabbed
- attempt to free an illegal
cmap entry
- attempt to store into a read-only
color map entry.
- attempt to modify the access control
list from other than the local host.
*/
BadAlloc = 11, /* insufficient resources */
BadColor = 12, /* no such colormap */
BadGC = 13, /* parameter not a GC */
BadIDChoice = 14, /* choice not in range or already used */
BadName = 15, /* font or color name doesn't exist */
BadLength = 16, /* Request length incorrect */
BadImplementation = 17, /* server is defective */
FirstExtensionError = 128,
LastExtensionError = 255,
}
[Flags]
public enum XEventMask : int
{
NoEventMask = 0,
KeyPressMask = (1 << 0),
KeyReleaseMask = (1 << 1),
ButtonPressMask = (1 << 2),
ButtonReleaseMask = (1 << 3),
EnterWindowMask = (1 << 4),
LeaveWindowMask = (1 << 5),
PointerMotionMask = (1 << 6),
PointerMotionHintMask = (1 << 7),
Button1MotionMask = (1 << 8),
Button2MotionMask = (1 << 9),
Button3MotionMask = (1 << 10),
Button4MotionMask = (1 << 11),
Button5MotionMask = (1 << 12),
ButtonMotionMask = (1 << 13),
KeymapStateMask = (1 << 14),
ExposureMask = (1 << 15),
VisibilityChangeMask = (1 << 16),
StructureNotifyMask = (1 << 17),
ResizeRedirectMask = (1 << 18),
SubstructureNotifyMask = (1 << 19),
SubstructureRedirectMask = (1 << 20),
FocusChangeMask = (1 << 21),
PropertyChangeMask = (1 << 22),
ColormapChangeMask = (1 << 23),
OwnerGrabButtonMask = (1 << 24)
}
[Flags]
public enum XModifierMask
{
ShiftMask = (1 << 0),
LockMask = (1 << 1),
ControlMask = (1 << 2),
Mod1Mask = (1 << 3),
Mod2Mask = (1 << 4),
Mod3Mask = (1 << 5),
Mod4Mask = (1 << 6),
Mod5Mask = (1 << 7),
Button1Mask = (1 << 8),
Button2Mask = (1 << 9),
Button3Mask = (1 << 10),
Button4Mask = (1 << 11),
Button5Mask = (1 << 12),
AnyModifier = (1 << 15)
}
[Flags]
public enum XCreateWindowFlags
{
CWBackPixmap = (1 << 0),
CWBackPixel = (1 << 1),
CWBorderPixmap = (1 << 2),
CWBorderPixel = (1 << 3),
CWBitGravity = (1 << 4),
CWWinGravity = (1 << 5),
CWBackingStore = (1 << 6),
CWBackingPlanes = (1 << 7),
CWBackingPixel = (1 << 8),
CWOverrideRedirect = (1 << 9),
CWSaveUnder = (1 << 10),
CWEventMask = (1 << 11),
CWDontPropagate = (1 << 12),
CWColormap = (1 << 13),
CWCursor = (1 << 14),
}
}

12
src/Avalonia.X11/X11Exception.cs

@ -0,0 +1,12 @@
using System;
namespace Avalonia.X11
{
public class X11Exception : Exception
{
public X11Exception(string message) : base(message)
{
}
}
}

59
src/Avalonia.X11/X11Framebuffer.cs

@ -0,0 +1,59 @@
using System;
using System.IO;
using Avalonia.Platform;
using SkiaSharp;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11Framebuffer : ILockedFramebuffer
{
private readonly IntPtr _display;
private readonly IntPtr _xid;
private readonly int _depth;
private IUnmanagedBlob _blob;
public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor)
{
_display = display;
_xid = xid;
_depth = depth;
Size = new PixelSize(width, height);
RowBytes = width * 4;
Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Bgra8888;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * height);
Address = _blob.Address;
}
public void Dispose()
{
var image = new XImage();
int bitsPerPixel = 32;
image.width = Size.Width;
image.height = Size.Height;
image.format = 2; //ZPixmap;
image.data = Address;
image.byte_order = 0;// LSBFirst;
image.bitmap_unit = bitsPerPixel;
image.bitmap_bit_order = 0;// LSBFirst;
image.bitmap_pad = bitsPerPixel;
image.depth = _depth;
image.bytes_per_line = RowBytes;
image.bits_per_pixel = bitsPerPixel;
XLockDisplay(_display);
XInitImage(ref image);
var gc = XCreateGC(_display, _xid, 0, IntPtr.Zero);
XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Size.Width, (uint) Size.Height);
XFreeGC(_display, gc);
XSync(_display, true);
XUnlockDisplay(_display);
_blob.Dispose();
}
public IntPtr Address { get; }
public PixelSize Size { get; }
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
}

29
src/Avalonia.X11/X11FramebufferSurface.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
public class X11FramebufferSurface : IFramebufferPlatformSurface
{
private readonly IntPtr _display;
private readonly IntPtr _xid;
private readonly Func<double> _scaling;
public X11FramebufferSurface(IntPtr display, IntPtr xid, Func<double> scaling)
{
_display = display;
_xid = xid;
_scaling = scaling;
}
public ILockedFramebuffer Lock()
{
XLockDisplay(_display);
XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height,
out var bw, out var d);
XUnlockDisplay(_display);
return new X11Framebuffer(_display, _xid, 24,width, height, _scaling());
}
}
}

105
src/Avalonia.X11/X11IconLoader.cs

@ -0,0 +1,105 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class X11IconLoader : IPlatformIconLoader
{
private readonly X11Info _x11;
public X11IconLoader(X11Info x11)
{
_x11 = x11;
}
IWindowIconImpl LoadIcon(Bitmap bitmap)
{
var rv = new X11IconData(bitmap);
bitmap.Dispose();
return rv;
}
public IWindowIconImpl LoadIcon(string fileName) => LoadIcon(new Bitmap(fileName));
public IWindowIconImpl LoadIcon(Stream stream) => LoadIcon(new Bitmap(stream));
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
var ms = new MemoryStream();
bitmap.Save(ms);
ms.Position = 0;
return LoadIcon(ms);
}
}
unsafe class X11IconData : IWindowIconImpl, IFramebufferPlatformSurface
{
private int _width;
private int _height;
private uint[] _bdata;
public UIntPtr[] Data { get; }
public X11IconData(Bitmap bitmap)
{
_width = Math.Min(bitmap.PixelSize.Width, 128);
_height = Math.Min(bitmap.PixelSize.Height, 128);
_bdata = new uint[_width * _height];
fixed (void* ptr = _bdata)
{
var iptr = (int*)ptr;
iptr[0] = _width;
iptr[1] = _height;
}
using(var rt = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext(null))
ctx.DrawImage(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));
Data = new UIntPtr[_width * _height + 2];
Data[0] = new UIntPtr((uint)_width);
Data[1] = new UIntPtr((uint)_height);
for (var y = 0; y < _height; y++)
{
var r = y * _width;
for (var x = 0; x < _width; x++)
Data[r + x] = new UIntPtr(_bdata[r + x]);
}
_bdata = null;
}
public void Save(Stream outputStream)
{
using (var wr =
new WriteableBitmap(new PixelSize(_width, _height), new Vector(96, 96), PixelFormat.Bgra8888))
{
using (var fb = wr.Lock())
{
var fbp = (uint*)fb.Address;
for (var y = 0; y < _height; y++)
{
var r = y * _width;
var fbr = y * fb.RowBytes / 4;
for (var x = 0; x < _width; x++)
fbp[fbr + x] = Data[r + x].ToUInt32();
}
}
wr.Save(outputStream);
}
}
public ILockedFramebuffer Lock()
{
var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned);
return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4,
new Vector(96, 96), PixelFormat.Bgra8888,
() => h.Free());
}
}
}

85
src/Avalonia.X11/X11Info.cs

@ -0,0 +1,85 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Avalonia.X11
{
class X11Info
{
public IntPtr Display { get; }
public IntPtr DeferredDisplay { get; }
public int DefaultScreen { get; }
public IntPtr BlackPixel { get; }
public IntPtr RootWindow { get; }
public IntPtr DefaultRootWindow { get; }
public IntPtr DefaultCursor { get; }
public X11Atoms Atoms { get; }
public IntPtr Xim { get; }
public int RandrEventBase { get; }
public int RandrErrorBase { get; }
public Version RandrVersion { get; }
public int XInputOpcode { get; }
public int XInputEventBase { get; }
public int XInputErrorBase { get; }
public Version XInputVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
public unsafe X11Info(IntPtr display, IntPtr deferredDisplay)
{
Display = display;
DeferredDisplay = deferredDisplay;
DefaultScreen = XDefaultScreen(display);
BlackPixel = XBlackPixel(display, DefaultScreen);
RootWindow = XRootWindow(display, DefaultScreen);
DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_top_left_arrow);
DefaultRootWindow = XDefaultRootWindow(display);
Atoms = new X11Atoms(display);
//TODO: Open an actual XIM once we get support for preedit in our textbox
XSetLocaleModifiers("@im=none");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
try
{
if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0)
{
RandrEventBase = randrEventBase;
RandrErrorBase = randrErrorBase;
if (XRRQueryVersion(display, out var major, out var minor) != 0)
RandrVersion = new Version(major, minor);
}
}
catch
{
//Ignore, randr is not supported
}
try
{
if (XQueryExtension(display, "XInputExtension",
out var xiopcode, out var xievent, out var xierror))
{
int major = 2, minor = 2;
if (XIQueryVersion(display, ref major, ref minor) == Status.Success)
{
XInputVersion = new Version(major, minor);
XInputOpcode = xiopcode;
XInputEventBase = xievent;
XInputErrorBase = xierror;
}
}
}
catch
{
//Ignore, XI is not supported
}
}
}
}

232
src/Avalonia.X11/X11KeyTransform.cs

@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.X11
{
static class X11KeyTransform
{
private static readonly Dictionary<X11Key, Key> KeyDic = new Dictionary<X11Key, Key>
{
{X11Key.Cancel, Key.Cancel},
{X11Key.BackSpace, Key.Back},
{X11Key.Tab, Key.Tab},
{X11Key.Linefeed, Key.LineFeed},
{X11Key.Clear, Key.Clear},
{X11Key.Return, Key.Return},
{X11Key.KP_Enter, Key.Return},
{X11Key.Pause, Key.Pause},
{X11Key.Caps_Lock, Key.CapsLock},
//{ X11Key.?, Key.HangulMode }
//{ X11Key.?, Key.JunjaMode }
//{ X11Key.?, Key.FinalMode }
//{ X11Key.?, Key.KanjiMode }
{X11Key.Escape, Key.Escape},
//{ X11Key.?, Key.ImeConvert }
//{ X11Key.?, Key.ImeNonConvert }
//{ X11Key.?, Key.ImeAccept }
//{ X11Key.?, Key.ImeModeChange }
{X11Key.space, Key.Space},
{X11Key.Prior, Key.Prior},
{X11Key.KP_Prior, Key.Prior},
{X11Key.Page_Down, Key.PageDown},
{X11Key.KP_Page_Down, Key.PageDown},
{X11Key.End, Key.End},
{X11Key.KP_End, Key.End},
{X11Key.Home, Key.Home},
{X11Key.KP_Home, Key.Home},
{X11Key.Left, Key.Left},
{X11Key.KP_Left, Key.Left},
{X11Key.Up, Key.Up},
{X11Key.KP_Up, Key.Up},
{X11Key.Right, Key.Right},
{X11Key.KP_Right, Key.Right},
{X11Key.Down, Key.Down},
{X11Key.KP_Down, Key.Down},
{X11Key.Select, Key.Select},
{X11Key.Print, Key.Print},
{X11Key.Execute, Key.Execute},
//{ X11Key.?, Key.Snapshot }
{X11Key.Insert, Key.Insert},
{X11Key.KP_Insert, Key.Insert},
{X11Key.Delete, Key.Delete},
{X11Key.KP_Delete, Key.Delete},
{X11Key.Help, Key.Help},
{X11Key.XK_0, Key.D0},
{X11Key.XK_1, Key.D1},
{X11Key.XK_2, Key.D2},
{X11Key.XK_3, Key.D3},
{X11Key.XK_4, Key.D4},
{X11Key.XK_5, Key.D5},
{X11Key.XK_6, Key.D6},
{X11Key.XK_7, Key.D7},
{X11Key.XK_8, Key.D8},
{X11Key.XK_9, Key.D9},
{X11Key.A, Key.A},
{X11Key.B, Key.B},
{X11Key.C, Key.C},
{X11Key.D, Key.D},
{X11Key.E, Key.E},
{X11Key.F, Key.F},
{X11Key.G, Key.G},
{X11Key.H, Key.H},
{X11Key.I, Key.I},
{X11Key.J, Key.J},
{X11Key.K, Key.K},
{X11Key.L, Key.L},
{X11Key.M, Key.M},
{X11Key.N, Key.N},
{X11Key.O, Key.O},
{X11Key.P, Key.P},
{X11Key.Q, Key.Q},
{X11Key.R, Key.R},
{X11Key.S, Key.S},
{X11Key.T, Key.T},
{X11Key.U, Key.U},
{X11Key.V, Key.V},
{X11Key.W, Key.W},
{X11Key.X, Key.X},
{X11Key.Y, Key.Y},
{X11Key.Z, Key.Z},
{X11Key.a, Key.A},
{X11Key.b, Key.B},
{X11Key.c, Key.C},
{X11Key.d, Key.D},
{X11Key.e, Key.E},
{X11Key.f, Key.F},
{X11Key.g, Key.G},
{X11Key.h, Key.H},
{X11Key.i, Key.I},
{X11Key.j, Key.J},
{X11Key.k, Key.K},
{X11Key.l, Key.L},
{X11Key.m, Key.M},
{X11Key.n, Key.N},
{X11Key.o, Key.O},
{X11Key.p, Key.P},
{X11Key.q, Key.Q},
{X11Key.r, Key.R},
{X11Key.s, Key.S},
{X11Key.t, Key.T},
{X11Key.u, Key.U},
{X11Key.v, Key.V},
{X11Key.w, Key.W},
{X11Key.x, Key.X},
{X11Key.y, Key.Y},
{X11Key.z, Key.Z},
//{ X11Key.?, Key.LWin }
//{ X11Key.?, Key.RWin }
{X11Key.Menu, Key.Apps},
//{ X11Key.?, Key.Sleep }
{X11Key.KP_0, Key.NumPad0},
{X11Key.KP_1, Key.NumPad1},
{X11Key.KP_2, Key.NumPad2},
{X11Key.KP_3, Key.NumPad3},
{X11Key.KP_4, Key.NumPad4},
{X11Key.KP_5, Key.NumPad5},
{X11Key.KP_6, Key.NumPad6},
{X11Key.KP_7, Key.NumPad7},
{X11Key.KP_8, Key.NumPad8},
{X11Key.KP_9, Key.NumPad9},
{X11Key.multiply, Key.Multiply},
{X11Key.KP_Multiply, Key.Multiply},
{X11Key.KP_Add, Key.Add},
//{ X11Key.?, Key.Separator }
{X11Key.KP_Subtract, Key.Subtract},
{X11Key.KP_Decimal, Key.Decimal},
{X11Key.KP_Divide, Key.Divide},
{X11Key.F1, Key.F1},
{X11Key.F2, Key.F2},
{X11Key.F3, Key.F3},
{X11Key.F4, Key.F4},
{X11Key.F5, Key.F5},
{X11Key.F6, Key.F6},
{X11Key.F7, Key.F7},
{X11Key.F8, Key.F8},
{X11Key.F9, Key.F9},
{X11Key.F10, Key.F10},
{X11Key.F11, Key.F11},
{X11Key.F12, Key.F12},
{X11Key.L3, Key.F13},
{X11Key.F14, Key.F14},
{X11Key.L5, Key.F15},
{X11Key.F16, Key.F16},
{X11Key.F17, Key.F17},
{X11Key.L8, Key.F18},
{X11Key.L9, Key.F19},
{X11Key.L10, Key.F20},
{X11Key.R1, Key.F21},
{X11Key.R2, Key.F22},
{X11Key.F23, Key.F23},
{X11Key.R4, Key.F24},
{X11Key.Num_Lock, Key.NumLock},
{X11Key.Scroll_Lock, Key.Scroll},
{X11Key.Shift_L, Key.LeftShift},
{X11Key.Shift_R, Key.RightShift},
{X11Key.Control_L, Key.LeftCtrl},
{X11Key.Control_R, Key.RightCtrl},
{X11Key.Alt_L, Key.LeftAlt},
{X11Key.Alt_R, Key.RightAlt},
//{ X11Key.?, Key.BrowserBack }
//{ X11Key.?, Key.BrowserForward }
//{ X11Key.?, Key.BrowserRefresh }
//{ X11Key.?, Key.BrowserStop }
//{ X11Key.?, Key.BrowserSearch }
//{ X11Key.?, Key.BrowserFavorites }
//{ X11Key.?, Key.BrowserHome }
//{ X11Key.?, Key.VolumeMute }
//{ X11Key.?, Key.VolumeDown }
//{ X11Key.?, Key.VolumeUp }
//{ X11Key.?, Key.MediaNextTrack }
//{ X11Key.?, Key.MediaPreviousTrack }
//{ X11Key.?, Key.MediaStop }
//{ X11Key.?, Key.MediaPlayPause }
//{ X11Key.?, Key.LaunchMail }
//{ X11Key.?, Key.SelectMedia }
//{ X11Key.?, Key.LaunchApplication1 }
//{ X11Key.?, Key.LaunchApplication2 }
{X11Key.semicolon, Key.OemSemicolon},
{X11Key.plus, Key.OemPlus},
{X11Key.equal, Key.OemPlus},
{X11Key.comma, Key.OemComma},
{X11Key.minus, Key.OemMinus},
{X11Key.period, Key.OemPeriod},
{X11Key.slash, Key.Oem2},
{X11Key.grave, Key.OemTilde},
//{ X11Key.?, Key.AbntC1 }
//{ X11Key.?, Key.AbntC2 }
{X11Key.bracketleft, Key.OemOpenBrackets},
{X11Key.backslash, Key.OemPipe},
{X11Key.bracketright, Key.OemCloseBrackets},
{X11Key.apostrophe, Key.OemQuotes},
//{ X11Key.?, Key.Oem8 }
//{ X11Key.?, Key.Oem102 }
//{ X11Key.?, Key.ImeProcessed }
//{ X11Key.?, Key.System }
//{ X11Key.?, Key.OemAttn }
//{ X11Key.?, Key.OemFinish }
//{ X11Key.?, Key.DbeHiragana }
//{ X11Key.?, Key.OemAuto }
//{ X11Key.?, Key.DbeDbcsChar }
//{ X11Key.?, Key.OemBackTab }
//{ X11Key.?, Key.Attn }
//{ X11Key.?, Key.DbeEnterWordRegisterMode }
//{ X11Key.?, Key.DbeEnterImeConfigureMode }
//{ X11Key.?, Key.EraseEof }
//{ X11Key.?, Key.Play }
//{ X11Key.?, Key.Zoom }
//{ X11Key.?, Key.NoName }
//{ X11Key.?, Key.DbeEnterDialogConversionMode }
//{ X11Key.?, Key.OemClear }
//{ X11Key.?, Key.DeadCharProcessed }
};
public static Key ConvertKey(IntPtr key)
{
var ikey = key.ToInt32();
Key result;
return KeyDic.TryGetValue((X11Key)ikey, out result) ? result : Key.None;
}
}
}

93
src/Avalonia.X11/X11Platform.cs

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.X11;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
class AvaloniaX11Platform : IWindowingPlatform
{
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice());
private Lazy<MouseDevice> _mouseDevice = new Lazy<MouseDevice>(() => new MouseDevice());
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public MouseDevice MouseDevice => _mouseDevice.Value;
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
public XI2Manager XI2;
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
public void Initialize()
{
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
if (Display == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
XError.Init();
Info = new X11Info(Display, DeferredDisplay);
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);
if (Info.XInputVersion != null)
{
var xi2 = new XI2Manager();
if (xi2.Init(this))
XI2 = xi2;
}
EglGlPlatformFeature.TryInitialize();
}
public IntPtr DeferredDisplay { get; set; }
public IntPtr Display { get; set; }
public IWindowImpl CreateWindow()
{
return new X11Window(this, false);
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
{
throw new NotSupportedException();
}
public IPopupImpl CreatePopup()
{
return new X11Window(this, true);
}
}
}
namespace Avalonia
{
public static class AvaloniaX11PlatformExtensions
{
public static T UseX11<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize());
return builder;
}
public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize();
}
}

285
src/Avalonia.X11/X11PlatformThreading.cs

@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class X11PlatformThreading : IPlatformThreadingInterface
{
private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _display;
private readonly Dictionary<IntPtr, Action<XEvent>> _eventHandlers;
private Thread _mainThread;
[StructLayout(LayoutKind.Explicit)]
struct epoll_data
{
[FieldOffset(0)]
public IntPtr ptr;
[FieldOffset(0)]
public int fd;
[FieldOffset(0)]
public uint u32;
[FieldOffset(0)]
public ulong u64;
}
private const int EPOLLIN = 1;
private const int EPOLL_CTL_ADD = 1;
private const int O_NONBLOCK = 2048;
[StructLayout(LayoutKind.Sequential)]
struct epoll_event
{
public uint events;
public epoll_data data;
}
[DllImport("libc")]
extern static int epoll_create1(int size);
[DllImport("libc")]
extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event);
[DllImport("libc")]
extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout);
[DllImport("libc")]
extern static int pipe2(int* fds, int flags);
[DllImport("libc")]
extern static IntPtr write(int fd, void* buf, IntPtr count);
[DllImport("libc")]
extern static IntPtr read(int fd, void* buf, IntPtr count);
enum EventCodes
{
X11 = 1,
Signal =2
}
private int _sigread, _sigwrite;
private object _lock = new object();
private bool _signaled;
private DispatcherPriority _signaledPriority;
private int _epoll;
private Stopwatch _clock = Stopwatch.StartNew();
class X11Timer : IDisposable
{
private readonly X11PlatformThreading _parent;
public X11Timer(X11PlatformThreading parent, DispatcherPriority prio, TimeSpan interval, Action tick)
{
_parent = parent;
Priority = prio;
Tick = tick;
Interval = interval;
Reschedule();
}
public DispatcherPriority Priority { get; }
public TimeSpan NextTick { get; private set; }
public TimeSpan Interval { get; }
public Action Tick { get; }
public bool Disposed { get; private set; }
public void Reschedule()
{
NextTick = _parent._clock.Elapsed + Interval;
}
public void Dispose()
{
Disposed = true;
lock (_parent._lock)
_parent._timers.Remove(this);
}
}
List<X11Timer> _timers = new List<X11Timer>();
public X11PlatformThreading(AvaloniaX11Platform platform)
{
_platform = platform;
_display = platform.Display;
_eventHandlers = platform.Windows;
_mainThread = Thread.CurrentThread;
var fd = XLib.XConnectionNumber(_display);
var ev = new epoll_event()
{
events = EPOLLIN,
data = {u32 = (int)EventCodes.X11}
};
_epoll = epoll_create1(0);
if (_epoll == -1)
throw new X11Exception("epoll_create1 failed");
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1)
throw new X11Exception("Unable to attach X11 connection handle to epoll");
var fds = stackalloc int[2];
pipe2(fds, O_NONBLOCK);
_sigread = fds[0];
_sigwrite = fds[1];
ev = new epoll_event
{
events = EPOLLIN,
data = {u32 = (int)EventCodes.Signal}
};
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1)
throw new X11Exception("Unable to attach signal pipe to epoll");
}
int TimerComparer(X11Timer t1, X11Timer t2)
{
return t2.Priority - t1.Priority;
}
void CheckSignaled()
{
int buf = 0;
while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0)
{
}
DispatcherPriority prio;
lock (_lock)
{
if (!_signaled)
return;
_signaled = false;
prio = _signaledPriority;
_signaledPriority = DispatcherPriority.MinValue;
}
Signaled?.Invoke(prio);
}
void HandleX11(CancellationToken cancellationToken)
{
while (true)
{
var pending = XPending(_display);
if (pending == 0)
break;
while (pending > 0)
{
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
pending--;
try
{
if (xev.type == XEventName.GenericEvent)
{
if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
xev.GenericEventCookie.extension)
{
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
}
finally
{
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
XFreeEventData(_display, &xev.GenericEventCookie);
}
}
}
Dispatcher.UIThread.RunJobs();
}
public void RunLoop(CancellationToken cancellationToken)
{
var readyTimers = new List<X11Timer>();
while (!cancellationToken.IsCancellationRequested)
{
var now = _clock.Elapsed;
TimeSpan? nextTick = null;
readyTimers.Clear();
lock(_timers)
foreach (var t in _timers)
{
if (nextTick == null || t.NextTick < nextTick.Value)
nextTick = t.NextTick;
if (t.NextTick < now)
readyTimers.Add(t);
}
readyTimers.Sort(TimerComparer);
foreach (var t in readyTimers)
{
if (cancellationToken.IsCancellationRequested)
return;
t.Tick();
if(!t.Disposed)
{
t.Reschedule();
if (nextTick == null || t.NextTick < nextTick.Value)
nextTick = t.NextTick;
}
}
if (cancellationToken.IsCancellationRequested)
return;
//Flush whatever requests were made to XServer
XFlush(_display);
epoll_event ev;
if (XPending(_display) == 0)
epoll_wait(_epoll, &ev, 1,
nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds));
if (cancellationToken.IsCancellationRequested)
return;
CheckSignaled();
HandleX11(cancellationToken);
}
}
public void Signal(DispatcherPriority priority)
{
lock (_lock)
{
if (priority > _signaledPriority)
_signaledPriority = priority;
if(_signaled)
return;
_signaled = true;
int buf = 0;
write(_sigwrite, &buf, new IntPtr(1));
}
}
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread;
public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
if (_mainThread != Thread.CurrentThread)
throw new InvalidOperationException("StartTimer can be only called from UI thread");
if (interval <= TimeSpan.Zero)
throw new ArgumentException("Interval must be positive", nameof(interval));
// We assume that we are on the main thread and outside of epoll_wait, so there is no need for wakeup signal
var timer = new X11Timer(this, priority, interval, tick);
lock(_timers)
_timers.Add(timer);
return timer;
}
}
}

252
src/Avalonia.X11/X11Screens.cs

@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using static Avalonia.X11.XLib;
using JetBrains.Annotations;
namespace Avalonia.X11
{
class X11Screens : IScreenImpl
{
private IX11Screens _impl;
public X11Screens(IX11Screens impl)
{
_impl = impl;
}
static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens)
{
var rect = default(Rect);
foreach (var s in screens)
{
rect = rect.Union(s.Bounds);
//Fallback value
s.WorkingArea = s.Bounds;
}
var res = XGetWindowProperty(info.Display,
info.RootWindow,
info.Atoms._NET_WORKAREA,
IntPtr.Zero,
new IntPtr(128),
false,
info.Atoms.AnyPropertyType,
out var type,
out var format,
out var count,
out var bytesAfter,
out var prop);
if (res != (int)Status.Success || type == IntPtr.Zero ||
format == 0 || bytesAfter.ToInt64() != 0 || count.ToInt64() % 4 != 0)
return screens;
var pwa = (IntPtr*)prop;
var wa = new Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
foreach (var s in screens)
s.WorkingArea = s.Bounds.Intersect(wa);
XFree(prop);
return screens;
}
class Randr15ScreensImpl : IX11Screens
{
private readonly X11ScreensUserSettings _settings;
private X11Screen[] _cache;
private X11Info _x11;
private IntPtr _window;
public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings)
{
_settings = settings;
_x11 = platform.Info;
_window = CreateEventWindow(platform, OnEvent);
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
}
private void OnEvent(XEvent ev)
{
// Invalidate cache on RRScreenChangeNotify
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify)
_cache = null;
}
public unsafe X11Screen[] Screens
{
get
{
if (_cache != null)
return _cache;
var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count);
var screens = new X11Screen[count];
for (var c = 0; c < count; c++)
{
var mon = monitors[c];
var namePtr = XGetAtomName(_x11.Display, mon.Name);
var name = Marshal.PtrToStringAnsi(namePtr);
XFree(namePtr);
var density = 1d;
if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true)
{
if (mon.MWidth == 0)
density = 1;
else
density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth);
}
density *= _settings.GlobalScaleFactor;
var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height);
screens[c] = new X11Screen(bounds,
mon.Primary != 0,
name,
(mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight),
density);
}
XFree(new IntPtr(monitors));
_cache = UpdateWorkArea(_x11, screens);
return screens;
}
}
}
class FallbackScreensImpl : IX11Screens
{
public FallbackScreensImpl(X11Info info, X11ScreensUserSettings settings)
{
if (XGetGeometry(info.Display, info.RootWindow, out var geo))
{
Screens = UpdateWorkArea(info,
new[]
{
new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null,
settings.GlobalScaleFactor)
});
}
Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
}
public X11Screen[] Screens { get; }
}
public static IX11Screens Init(AvaloniaX11Platform platform)
{
var info = platform.Info;
var settings = X11ScreensUserSettings.Detect();
var impl = (info.RandrVersion != null && info.RandrVersion >= new Version(1, 5))
? new Randr15ScreensImpl(platform, settings)
: (IX11Screens)new FallbackScreensImpl(info, settings);
return impl;
}
public int ScreenCount => _impl.Screens.Length;
public Screen[] AllScreens =>
_impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray();
}
interface IX11Screens
{
X11Screen[] Screens { get; }
}
class X11ScreensUserSettings
{
public double GlobalScaleFactor { get; set; } = 1;
public Dictionary<string, double> NamedScaleFactors { get; set; }
static double? TryParse(string s)
{
if (s == null)
return null;
if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var rv))
return rv;
return null;
}
public static X11ScreensUserSettings DetectEnvironment()
{
var globalFactor = Environment.GetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR");
var screenFactors = Environment.GetEnvironmentVariable("AVALONIA_SCREEN_SCALE_FACTORS");
if (globalFactor == null && screenFactors == null)
return null;
var rv = new X11ScreensUserSettings
{
GlobalScaleFactor = TryParse(globalFactor) ?? 1
};
try
{
if (!string.IsNullOrWhiteSpace(screenFactors))
{
rv.NamedScaleFactors = screenFactors.Split(';').Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Split('=')).ToDictionary(x => x[0],
x => double.Parse(x[1], CultureInfo.InvariantCulture));
}
}
catch
{
//Ignore
}
return rv;
}
public static X11ScreensUserSettings Detect()
{
return DetectEnvironment() ?? new X11ScreensUserSettings();
}
}
class X11Screen
{
public bool Primary { get; }
public string Name { get; set; }
public Rect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
public double PixelDensity { get; set; }
public Rect WorkingArea { get; set; }
public X11Screen(Rect bounds, bool primary,
string name, Size? physicalSize, double? pixelDensity)
{
Primary = primary;
Name = name;
Bounds = bounds;
if (physicalSize == null && pixelDensity == null)
{
PixelDensity = 1;
}
else if (pixelDensity == null)
{
PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width);
}
else
{
PixelDensity = pixelDensity.Value;
PhysicalSize = physicalSize;
}
}
public static double GuessPixelDensity(double pixelWidth, double mmWidth)
=> Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
}
}

1886
src/Avalonia.X11/X11Structs.cs

File diff suppressed because it is too large

873
src/Avalonia.X11/X11Window.cs

@ -0,0 +1,873 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using static Avalonia.X11.XLib;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
namespace Avalonia.X11
{
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client
{
private readonly AvaloniaX11Platform _platform;
private readonly bool _popup;
private readonly X11Info _x11;
private bool _invalidated;
private XConfigureEvent? _configure;
private Point? _configurePoint;
private bool _triggeredExpose;
private IInputRoot _inputRoot;
private readonly IMouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private Point? _position;
private PixelSize _realSize;
private IntPtr _handle;
private IntPtr _xic;
private IntPtr _renderHandle;
private bool _mapped;
private HashSet<X11Window> _transientChildren = new HashSet<X11Window>();
private X11Window _transientParent;
public object SyncRoot { get; } = new object();
class InputEventContainer
{
public RawInputEventArgs Event;
}
private readonly Queue<InputEventContainer> _inputQueue = new Queue<InputEventContainer>();
private InputEventContainer _lastEvent;
public X11Window(AvaloniaX11Platform platform, bool popup)
{
_platform = platform;
_popup = popup;
_x11 = platform.Info;
_mouse = platform.MouseDevice;
_keyboard = platform.KeyboardDevice;
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
attr.backing_store = 1;
attr.bit_gravity = Gravity.NorthWestGravity;
attr.win_gravity = Gravity.NorthWestGravity;
valueMask |= SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel
| SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore
| SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity;
if (popup)
{
attr.override_redirect = true;
valueMask |= SetWindowValuemask.OverrideRedirect;
}
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0,
24,
(int)CreateWindowArgs.InputOutput, IntPtr.Zero,
new UIntPtr((uint)valueMask), ref attr);
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24,
(int)CreateWindowArgs.InputOutput,
IntPtr.Zero,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(300, 200);
platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
| XEventMask.ResizeRedirectMask
| XEventMask.PointerMotionHintMask;
if (platform.XI2 != null)
ignoredMask |= platform.XI2.AddWindow(_handle, this);
var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
XSelectInput(_x11.Display, _handle, mask);
var protocols = new[]
{
_x11.Atoms.WM_DELETE_WINDOW
};
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
var surfaces = new List<object>
{
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling)
};
if (feature != null)
surfaces.Insert(0,
new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext,
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
Surfaces = surfaces.ToArray();
UpdateMotifHints();
XFlush(_x11.Display);
}
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
{
private readonly X11Window _window;
private readonly IntPtr _display;
private readonly IntPtr _parent;
public SurfaceInfo(X11Window window, IntPtr display, IntPtr parent, IntPtr xid)
{
_window = window;
_display = display;
_parent = parent;
Handle = xid;
}
public IntPtr Handle { get; }
public PixelSize Size
{
get
{
XLockDisplay(_display);
XGetGeometry(_display, _parent, out var geo);
XResizeWindow(_display, Handle, geo.width, geo.height);
XFlush(_display);
XSync(_display, true);
XUnlockDisplay(_display);
return new PixelSize(geo.width, geo.height);
}
}
public double Scaling => _window.Scaling;
}
void UpdateMotifHints()
{
var functions = MotifFunctions.Move | MotifFunctions.Close | MotifFunctions.Resize |
MotifFunctions.Minimize | MotifFunctions.Maximize;
var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border |
MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH;
if (_popup || !_systemDecorations)
{
decorations = 0;
}
if (!_canResize)
{
functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize);
decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH);
}
var hints = new MotifWmHints
{
flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)),
decorations = new IntPtr((int)decorations),
functions = new IntPtr((int)functions)
};
XChangeProperty(_x11.Display, _handle,
_x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32,
PropertyMode.Replace, ref hints, 5);
}
void UpdateSizeHints(PixelSize? preResize)
{
var min = _minMaxSize.minSize;
var max = _minMaxSize.maxSize;
if (!_canResize)
max = min = _realSize;
if (preResize.HasValue)
{
var desired = preResize.Value;
max = new PixelSize(Math.Max(desired.Width, max.Width), Math.Max(desired.Height, max.Height));
min = new PixelSize(Math.Min(desired.Width, min.Width), Math.Min(desired.Height, min.Height));
}
var hints = new XSizeHints
{
min_width = min.Width,
min_height = min.Height
};
hints.height_inc = hints.width_inc = 1;
var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc;
// People might be passing double.MaxValue
if (max.Width < 100000 && max.Height < 100000)
{
hints.max_width = max.Width;
hints.max_height = max.Height;
flags |= XSizeHintsFlags.PMaxSize;
}
hints.flags = (IntPtr)flags;
XSetWMNormalHints(_x11.Display, _handle, ref hints);
}
public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
public double Scaling
{
get
{
lock (SyncRoot)
return _scaling;
}
private set => _scaling = value;
}
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
//TODO
public Action<double> ScalingChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Func<bool> Closing { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action Closed { get; set; }
public Action<Point> PositionChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
void OnEvent(XEvent ev)
{
lock (SyncRoot)
OnEventSync(ev);
}
void OnEventSync(XEvent ev)
{
if(XFilterEvent(ref ev, _handle))
return;
if (ev.type == XEventName.MapNotify)
{
_mapped = true;
XMapWindow(_x11.Display, _renderHandle);
}
else if (ev.type == XEventName.UnmapNotify)
_mapped = false;
else if (ev.type == XEventName.Expose)
{
if (!_triggeredExpose)
{
_triggeredExpose = true;
Dispatcher.UIThread.Post(() =>
{
_triggeredExpose = false;
DoPaint();
}, DispatcherPriority.Render);
}
}
else if (ev.type == XEventName.FocusIn)
{
if (ActivateTransientChildIfNeeded())
return;
Activated?.Invoke();
}
else if (ev.type == XEventName.FocusOut)
Deactivated?.Invoke();
else if (ev.type == XEventName.MotionNotify)
MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
else if (ev.type == XEventName.PropertyNotify)
{
OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0);
}
else if (ev.type == XEventName.ButtonPress)
{
if (ActivateTransientChildIfNeeded())
return;
if (ev.ButtonEvent.button < 4)
MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown
: ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown
: RawMouseEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
else
{
var delta = ev.ButtonEvent.button == 4
? new Vector(0, 1)
: ev.ButtonEvent.button == 5
? new Vector(0, -1)
: ev.ButtonEvent.button == 6
? new Vector(1, 0)
: new Vector(-1, 0);
ScheduleInput(new RawMouseWheelEventArgs(_mouse, (ulong)ev.ButtonEvent.time.ToInt64(),
_inputRoot, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), delta,
TranslateModifiers(ev.ButtonEvent.state)), ref ev);
}
}
else if (ev.type == XEventName.ButtonRelease)
{
if (ev.ButtonEvent.button < 4)
MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp
: ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp
: RawMouseEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
}
else if (ev.type == XEventName.ConfigureNotify)
{
if (ev.ConfigureEvent.window != _handle)
return;
var needEnqueue = (_configure == null);
_configure = ev.ConfigureEvent;
if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event)
_configurePoint = new Point(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
else
{
XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow,
0, 0,
out var tx, out var ty, out _);
_configurePoint = new Point(tx, ty);
}
if (needEnqueue)
Dispatcher.UIThread.Post(() =>
{
if (_configure == null)
return;
var cev = _configure.Value;
var npos = _configurePoint.Value;
_configure = null;
_configurePoint = null;
var nsize = new PixelSize(cev.width, cev.height);
var changedSize = _realSize != nsize;
var changedPos = _position == null || npos != _position;
_realSize = nsize;
_position = npos;
bool updatedSizeViaScaling = false;
if (changedPos)
{
PositionChanged?.Invoke(npos);
updatedSizeViaScaling = UpdateScaling();
}
if (changedSize && !updatedSizeViaScaling)
Resized?.Invoke(ClientSize);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}, DispatcherPriority.Layout);
XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height);
}
else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle)
{
Cleanup();
}
else if (ev.type == XEventName.ClientMessage)
{
if (ev.ClientMessageEvent.message_type == _x11.Atoms.WM_PROTOCOLS)
{
if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW)
{
if (Closing?.Invoke() != true)
Dispose();
}
}
}
else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease)
{
if (ActivateTransientChildIfNeeded())
return;
var buffer = stackalloc byte[40];
var latinKeysym = XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, 0);
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(),
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
X11KeyTransform.ConvertKey(latinKeysym), TranslateModifiers(ev.KeyEvent.state)), ref ev);
if (ev.type == XEventName.KeyPress)
{
var len = Xutf8LookupString(_xic, ref ev, buffer, 40, out _, out _);
if (len != 0)
{
var text = Encoding.UTF8.GetString(buffer, len);
if (text.Length == 1)
{
if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
return;
}
ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text),
ref ev);
}
}
}
}
private bool UpdateScaling()
{
lock (SyncRoot)
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
var newScaling = monitor?.PixelDensity ?? Scaling;
if (Scaling != newScaling)
{
Console.WriteLine(
$"Updating scaling from {Scaling} to {newScaling} as a response to position change to {Position}");
var oldScaledSize = ClientSize;
Scaling = newScaling;
ScalingChanged?.Invoke(Scaling);
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
Resize(oldScaledSize, true);
return true;
}
return false;
}
}
private WindowState _lastWindowState;
public WindowState WindowState
{
get => _lastWindowState;
set
{
if(_lastWindowState == value)
return;
_lastWindowState = value;
if (value == WindowState.Minimized)
{
XIconifyWindow(_x11.Display, _handle, _x11.DefaultScreen);
}
else if (value == WindowState.Maximized)
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
}
else
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
}
}
}
private void OnPropertyChange(IntPtr atom, bool hasValue)
{
if (atom == _x11.Atoms._NET_WM_STATE)
{
WindowState state = WindowState.Normal;
if(hasValue)
{
XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _,
out var prop);
int maximized = 0;
var pitems = (IntPtr*)prop.ToPointer();
for (var c = 0; c < nitems.ToInt32(); c++)
{
if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN)
{
state = WindowState.Minimized;
break;
}
if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ ||
pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT)
{
maximized++;
if (maximized == 2)
{
state = WindowState.Maximized;
break;
}
}
}
XFree(prop);
}
if (_lastWindowState != state)
{
_lastWindowState = state;
WindowStateChanged?.Invoke(state);
}
}
}
InputModifiers TranslateModifiers(XModifierMask state)
{
var rv = default(InputModifiers);
if (state.HasFlag(XModifierMask.Button1Mask))
rv |= InputModifiers.LeftMouseButton;
if (state.HasFlag(XModifierMask.Button2Mask))
rv |= InputModifiers.RightMouseButton;
if (state.HasFlag(XModifierMask.Button2Mask))
rv |= InputModifiers.MiddleMouseButton;
if (state.HasFlag(XModifierMask.ShiftMask))
rv |= InputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
rv |= InputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
rv |= InputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
rv |= InputModifiers.Windows;
return rv;
}
private bool _systemDecorations = true;
private bool _canResize = true;
private (Size minSize, Size maxSize) _scaledMinMaxSize;
private (PixelSize minSize, PixelSize maxSize) _minMaxSize;
private double _scaling = 1;
void ScheduleInput(RawInputEventArgs args, ref XEvent xev)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
ScheduleInput(args);
}
public void ScheduleInput(RawInputEventArgs args)
{
if (args is RawMouseEventArgs mouse)
mouse.Position = mouse.Position / Scaling;
if (args is RawDragEvent drag)
drag.Location = drag.Location / Scaling;
_lastEvent = new InputEventContainer() {Event = args};
_inputQueue.Enqueue(_lastEvent);
if (_inputQueue.Count == 1)
{
Dispatcher.UIThread.Post(() =>
{
while (_inputQueue.Count > 0)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var ev = _inputQueue.Dequeue();
Input?.Invoke(ev.Event);
}
}, DispatcherPriority.Input);
}
}
void MouseEvent(RawMouseEventType type, ref XEvent ev, XModifierMask mods)
{
var mev = new RawMouseEventArgs(
_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma)
if (ma.Type == RawMouseEventType.Move)
{
_lastEvent.Event = mev;
return;
}
ScheduleInput(mev, ref ev);
}
void DoPaint()
{
_invalidated = false;
Paint?.Invoke(new Rect());
}
public void Invalidate(Rect rect)
{
if(_invalidated)
return;
_invalidated = true;
Dispatcher.UIThread.InvokeAsync(() =>
{
if (_mapped)
DoPaint();
});
}
public IInputRoot InputRoot => _inputRoot;
public void SetInputRoot(IInputRoot inputRoot)
{
_inputRoot = inputRoot;
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _handle);
Cleanup();
}
}
void Cleanup()
{
SetTransientParent(null, false);
if (_xic != IntPtr.Zero)
{
XDestroyIC(_xic);
_xic = IntPtr.Zero;
}
if (_handle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _handle);
_platform.Windows.Remove(_handle);
_platform.XI2?.OnWindowDestroyed(_handle);
_handle = IntPtr.Zero;
Closed?.Invoke();
}
if (_renderHandle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _renderHandle);
_renderHandle = IntPtr.Zero;
}
}
bool ActivateTransientChildIfNeeded()
{
if (_transientChildren.Count == 0)
return false;
var child = _transientChildren.First();
if (!child.ActivateTransientChildIfNeeded())
child.Activate();
return true;
}
void SetTransientParent(X11Window window, bool informServer = true)
{
_transientParent?._transientChildren.Remove(this);
_transientParent = window;
_transientParent?._transientChildren.Add(this);
if (informServer)
XSetTransientForHint(_x11.Display, _handle, _transientParent?._handle ?? IntPtr.Zero);
}
public void Show()
{
SetTransientParent(null);
ShowCore();
}
void ShowCore()
{
XMapWindow(_x11.Display, _handle);
XFlush(_x11.Display);
}
public void Hide() => XUnmapWindow(_x11.Display, _handle);
public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y);
public void SetSystemDecorations(bool enabled)
{
_systemDecorations = enabled;
UpdateMotifHints();
}
public void Resize(Size clientSize) => Resize(clientSize, false);
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
void Resize(Size clientSize, bool force)
{
if (!force && clientSize == ClientSize)
return;
var needImmediatePopupResize = clientSize != ClientSize;
var pixelSize = ToPixelSize(clientSize);
UpdateSizeHints(pixelSize);
XConfigureResizeWindow(_x11.Display, _handle, pixelSize);
XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
XFlush(_x11.Display);
if (force || (_popup && needImmediatePopupResize))
{
_realSize = pixelSize;
Resized?.Invoke(ClientSize);
}
}
public void CanResize(bool value)
{
_canResize = value;
UpdateMotifHints();
UpdateSizeHints(null);
}
public void SetCursor(IPlatformHandle cursor)
{
if (cursor == null)
XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor);
else
{
if (cursor.HandleDescriptor != "XCURSOR")
throw new ArgumentException("Expected XCURSOR handle type");
XDefineCursor(_x11.Display, _handle, cursor.Handle);
}
}
public IPlatformHandle Handle { get; }
public Point Position
{
get => _position ?? default;
set
{
var changes = new XWindowChanges
{
x = (int)value.X,
y = (int)value.Y
};
XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY,
ref changes);
XFlush(_x11.Display);
}
}
public IMouseDevice MouseDevice => _mouse;
public void Activate()
{
if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero)
{
SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp,
IntPtr.Zero);
}
else
{
XRaiseWindow(_x11.Display, _handle);
XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero);
}
}
public IScreenImpl Screen => _platform.Screens;
public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity)
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
void SendNetWMMessage(IntPtr message_type, IntPtr l0,
IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null)
{
var xev = new XEvent
{
ClientMessageEvent =
{
type = XEventName.ClientMessage,
send_event = true,
window = _handle,
message_type = message_type,
format = 32,
ptr1 = l0,
ptr2 = l1 ?? IntPtr.Zero,
ptr3 = l2 ?? IntPtr.Zero,
ptr4 = l3 ?? IntPtr.Zero
}
};
xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero;
XSendEvent(_x11.Display, _x11.RootWindow, false,
new IntPtr((int)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev);
}
void BeginMoveResize(NetWmMoveResize side)
{
var pos = GetCursorPos(_x11);
XUngrabPointer(_x11.Display, new IntPtr(0));
SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y,
(IntPtr) side,
(IntPtr) 1, (IntPtr)1); // left button
}
public void BeginMoveDrag()
{
BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE);
}
public void BeginResizeDrag(WindowEdge edge)
{
var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL;
if (edge == WindowEdge.East)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_RIGHT;
if (edge == WindowEdge.North)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOP;
if (edge == WindowEdge.South)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOM;
if (edge == WindowEdge.West)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_LEFT;
if (edge == WindowEdge.NorthEast)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPRIGHT;
if (edge == WindowEdge.NorthWest)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPLEFT;
if (edge == WindowEdge.SouthEast)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT;
if (edge == WindowEdge.SouthWest)
side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT;
BeginMoveResize(side);
}
public void SetTitle(string title)
{
var data = Encoding.UTF8.GetBytes(title);
fixed (void* pdata = data)
{
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8,
PropertyMode.Replace, pdata, data.Length);
XStoreName(_x11.Display, _handle, title);
}
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
_scaledMinMaxSize = (minSize, maxSize);
var min = new PixelSize(
(int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
(int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
const int maxDim = 100000;
var max = new PixelSize(
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, minSize.Width * Scaling)),
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, minSize.Height * Scaling)));
_minMaxSize = (min, max);
UpdateSizeHints(null);
}
public void SetTopmost(bool value)
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
(IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero);
}
public void ShowDialog(IWindowImpl parent)
{
SetTransientParent((X11Window)parent);
ShowCore();
}
public void SetIcon(IWindowIconImpl icon)
{
var data = ((X11IconData)icon).Data;
fixed (void* pdata = data)
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
pdata, data.Length);
}
public void ShowTaskbarIcon(bool value)
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
(IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
}
}
}

30
src/Avalonia.X11/XError.cs

@ -0,0 +1,30 @@
using System;
namespace Avalonia.X11
{
static class XError
{
private static readonly XErrorHandler s_errorHandlerDelegate = Handler;
public static XErrorEvent LastError;
static int Handler(IntPtr display, ref XErrorEvent error)
{
LastError = error;
return 0;
}
public static void ThrowLastError(string desc)
{
var err = LastError;
LastError = new XErrorEvent();
if (err.error_code == 0)
throw new X11Exception(desc);
throw new X11Exception(desc + ": " + err.error_code);
}
public static void Init()
{
XLib.XSetErrorHandler(s_errorHandlerDelegate);
}
}
}

269
src/Avalonia.X11/XI2Manager.cs

@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Input;
using Avalonia.Input.Raw;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
unsafe class XI2Manager
{
private X11Info _x11;
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
class DeviceInfo
{
public int Id { get; }
public XIValuatorClassInfo[] Valuators { get; private set; }
public XIScrollClassInfo[] Scrollers { get; private set; }
public DeviceInfo(XIDeviceInfo info)
{
Id = info.Deviceid;
Update(info.Classes, info.NumClasses);
}
public virtual void Update(XIAnyClassInfo** classes, int num)
{
var valuators = new List<XIValuatorClassInfo>();
var scrollers = new List<XIScrollClassInfo>();
for (var c = 0; c < num; c++)
{
if (classes[c]->Type == XiDeviceClass.XIValuatorClass)
valuators.Add(*((XIValuatorClassInfo**)classes)[c]);
if (classes[c]->Type == XiDeviceClass.XIScrollClass)
scrollers.Add(*((XIScrollClassInfo**)classes)[c]);
}
Valuators = valuators.ToArray();
Scrollers = scrollers.ToArray();
}
public void UpdateValuators(Dictionary<int, double> valuators)
{
foreach (var v in valuators)
{
if (Valuators.Length > v.Key)
Valuators[v.Key].Value = v.Value;
}
}
}
class PointerDeviceInfo : DeviceInfo
{
public PointerDeviceInfo(XIDeviceInfo info) : base(info)
{
}
public bool HasScroll(ParsedDeviceEvent ev)
{
foreach (var val in ev.Valuators)
if (Scrollers.Any(s => s.Number == val.Key))
return true;
return false;
}
public bool HasMotion(ParsedDeviceEvent ev)
{
foreach (var val in ev.Valuators)
if (Scrollers.All(s => s.Number != val.Key))
return true;
return false;
}
}
private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform;
public bool Init(AvaloniaX11Platform platform)
{
_platform = platform;
_x11 = platform.Info;
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display,
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num);
for (var c = 0; c < num; c++)
{
if (devices[c].Use == XiDeviceType.XIMasterPointer)
{
_pointerDevice = new PointerDeviceInfo(devices[c]);
break;
}
}
if(_pointerDevice == null)
return false;
/*
int mask = 0;
XISetMask(ref mask, XiEventType.XI_DeviceChanged);
var emask = new XIEventMask
{
Mask = &mask,
Deviceid = _pointerDevice.Id,
MaskLen = XiEventMaskLen
};
if (XISelectEvents(_x11.Display, _x11.RootWindow, &emask, 1) != Status.Success)
return false;
return true;
*/
return XiSelectEvents(_x11.Display, _x11.RootWindow, new Dictionary<int, List<XiEventType>>
{
[_pointerDevice.Id] = new List<XiEventType>
{
XiEventType.XI_DeviceChanged
}
}) == Status.Success;
}
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
XiSelectEvents(_x11.Display, xid, new Dictionary<int, List<XiEventType>>
{
[_pointerDevice.Id] = new List<XiEventType>()
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease,
}
});
// We are taking over mouse input handling from here
return XEventMask.PointerMotionMask
| XEventMask.ButtonMotionMask
| XEventMask.Button1MotionMask
| XEventMask.Button2MotionMask
| XEventMask.Button3MotionMask
| XEventMask.Button4MotionMask
| XEventMask.Button5MotionMask
| XEventMask.ButtonPressMask
| XEventMask.ButtonReleaseMask;
}
public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid);
public void OnEvent(XIEvent* xev)
{
if (xev->evtype == XiEventType.XI_DeviceChanged)
{
var changed = (XIDeviceChangedEvent*)xev;
_pointerDevice.Update(changed->Classes, changed->NumClasses);
}
//TODO: this should only be used for non-touch devices
if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
{
var dev = (XIDeviceEvent*)xev;
if (_clients.TryGetValue(dev->EventWindow, out var client))
OnDeviceEvent(client, new ParsedDeviceEvent(dev));
}
}
void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev)
{
if (ev.Type == XiEventType.XI_Motion)
{
Vector scrollDelta = default;
foreach (var v in ev.Valuators)
{
foreach (var scroller in _pointerDevice.Scrollers)
{
if (scroller.Number == v.Key)
{
var old = _pointerDevice.Valuators[scroller.Number].Value;
// Value was zero after reset, ignore the event and use it as a reference next time
if (old == 0)
continue;
var diff = (old - v.Value) / scroller.Increment;
if (scroller.ScrollType == XiScrollType.Horizontal)
scrollDelta = scrollDelta.WithX(scrollDelta.X + diff);
else
scrollDelta = scrollDelta.WithY(scrollDelta.Y + diff);
}
}
}
if (scrollDelta != default)
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
RawMouseEventType.Move, ev.Position, ev.Modifiers));
}
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease)
{
var down = ev.Type == XiEventType.XI_ButtonPress;
var type =
ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp)
: ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp)
: ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp)
: (RawMouseEventType?)null;
if (type.HasValue)
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
_pointerDevice.UpdateValuators(ev.Valuators);
}
}
unsafe class ParsedDeviceEvent
{
public XiEventType Type { get; }
public InputModifiers Modifiers { get; }
public ulong Timestamp { get; }
public Point Position { get; }
public int Button { get; set; }
public Dictionary<int, double> Valuators { get; }
public ParsedDeviceEvent(XIDeviceEvent* ev)
{
Type = ev->evtype;
Timestamp = (ulong)ev->time.ToInt64();
var state = (XModifierMask)ev->mods.Effective;
if (state.HasFlag(XModifierMask.ShiftMask))
Modifiers |= InputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
Modifiers |= InputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
Modifiers |= InputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
Modifiers |= InputModifiers.Windows;
if (ev->buttons.MaskLen > 0)
{
var buttons = ev->buttons.Mask;
if (XIMaskIsSet(buttons, 1))
Modifiers |= InputModifiers.LeftMouseButton;
if (XIMaskIsSet(buttons, 2))
Modifiers |= InputModifiers.MiddleMouseButton;
if (XIMaskIsSet(buttons, 3))
Modifiers |= InputModifiers.RightMouseButton;
}
Valuators = new Dictionary<int, double>();
Position = new Point(ev->event_x, ev->event_y);
var values = ev->valuators.Values;
for (var c = 0; c < ev->valuators.MaskLen * 8; c++)
if (XIMaskIsSet(ev->valuators.Mask, c))
Valuators[c] = *values++;
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
}
}
interface IXI2Client
{
IInputRoot InputRoot { get; }
void ScheduleInput(RawInputEventArgs args);
}
}

283
src/Avalonia.X11/XIStructs.cs

@ -0,0 +1,283 @@
using System;
using System.Runtime.InteropServices;
using Bool = System.Boolean;
using Atom = System.IntPtr;
// ReSharper disable IdentifierTypo
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable MemberCanBePrivate.Global
#pragma warning disable 649
namespace Avalonia.X11
{
[StructLayout(LayoutKind.Sequential)]
struct XIAddMasterInfo
{
public int Type;
public IntPtr Name;
public Bool SendCore;
public Bool Enable;
}
[StructLayout(LayoutKind.Sequential)]
struct XIRemoveMasterInfo
{
public int Type;
public int Deviceid;
public int ReturnMode; /* AttachToMaster, Floating */
public int ReturnPointer;
public int ReturnKeyboard;
};
[StructLayout(LayoutKind.Sequential)]
struct XIAttachSlaveInfo
{
public int Type;
public int Deviceid;
public int NewMaster;
};
[StructLayout(LayoutKind.Sequential)]
struct XIDetachSlaveInfo
{
public int Type;
public int Deviceid;
};
[StructLayout(LayoutKind.Explicit)]
struct XIAnyHierarchyChangeInfo
{
[FieldOffset(0)]
public int type; /* must be first element */
[FieldOffset(4)]
public XIAddMasterInfo add;
[FieldOffset(4)]
public XIRemoveMasterInfo remove;
[FieldOffset(4)]
public XIAttachSlaveInfo attach;
[FieldOffset(4)]
public XIDetachSlaveInfo detach;
};
[StructLayout(LayoutKind.Sequential)]
struct XIModifierState
{
public int Base;
public int Latched;
public int Locked;
public int Effective;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIButtonState
{
public int MaskLen;
public byte* Mask;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIValuatorState
{
public int MaskLen;
public byte* Mask;
public double* Values;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEventMask
{
public int Deviceid;
public int MaskLen;
public int* Mask;
};
[StructLayout(LayoutKind.Sequential)]
struct XIAnyClassInfo
{
public XiDeviceClass Type;
public int Sourceid;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIButtonClassInfo
{
public int Type;
public int Sourceid;
public int NumButtons;
public IntPtr* Labels;
public XIButtonState State;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIKeyClassInfo
{
public int Type;
public int Sourceid;
public int NumKeycodes;
public int* Keycodes;
};
[StructLayout(LayoutKind.Sequential)]
struct XIValuatorClassInfo
{
public int Type;
public int Sourceid;
public int Number;
public IntPtr Label;
public double Min;
public double Max;
public double Value;
public int Resolution;
public int Mode;
};
/* new in XI 2.1 */
[StructLayout(LayoutKind.Sequential)]
struct XIScrollClassInfo
{
public int Type;
public int Sourceid;
public int Number;
public XiScrollType ScrollType;
public double Increment;
public int Flags;
};
enum XiScrollType
{
Vertical = 1,
Horizontal = 2
}
[StructLayout(LayoutKind.Sequential)]
struct XITouchClassInfo
{
public int Type;
public int Sourceid;
public int Mode;
public int NumTouches;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIDeviceInfo
{
public int Deviceid;
public IntPtr Name;
public XiDeviceType Use;
public int Attachment;
public Bool Enabled;
public int NumClasses;
public XIAnyClassInfo** Classes;
}
enum XiDeviceType
{
XIMasterPointer = 1,
XIMasterKeyboard = 2,
XISlavePointer = 3,
XISlaveKeyboard = 4,
XIFloatingSlave = 5
}
enum XiPredefinedDeviceId : int
{
XIAllDevices = 0,
XIAllMasterDevices = 1
}
enum XiDeviceClass
{
XIKeyClass = 0,
XIButtonClass = 1,
XIValuatorClass = 2,
XIScrollClass = 3,
XITouchClass = 8,
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIDeviceChangedEvent
{
public int Type; /* GenericEvent */
public ulong Serial; /* # of last request processed by server */
public Bool SendEvent; /* true if this came from a SendEvent request */
public IntPtr Display; /* Display the event was read from */
public int Extension; /* XI extension offset */
public int Evtype; /* XI_DeviceChanged */
public IntPtr Time;
public int Deviceid; /* id of the device that changed */
public int Sourceid; /* Source for the new classes. */
public int Reason; /* Reason for the change */
public int NumClasses;
public XIAnyClassInfo** Classes; /* same as in XIDeviceInfo */
}
[StructLayout(LayoutKind.Sequential)]
struct XIDeviceEvent
{
public XEventName type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
public XiEventType evtype;
public IntPtr time;
public int deviceid;
public int sourceid;
public int detail;
public IntPtr RootWindow;
public IntPtr EventWindow;
public IntPtr ChildWindow;
public double root_x;
public double root_y;
public double event_x;
public double event_y;
public int flags;
public XIButtonState buttons;
public XIValuatorState valuators;
public XIModifierState mods;
public XIModifierState group;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEvent
{
public int type; /* GenericEvent */
public ulong serial; /* # of last request processed by server */
public Bool send_event; /* true if this came from a SendEvent request */
public IntPtr display; /* Display the event was read from */
public int extension; /* XI extension offset */
public XiEventType evtype;
public IntPtr time;
}
enum XiEventType
{
XI_DeviceChanged = 1,
XI_KeyPress = 2,
XI_KeyRelease = 3,
XI_ButtonPress = 4,
XI_ButtonRelease = 5,
XI_Motion = 6,
XI_Enter = 7,
XI_Leave = 8,
XI_FocusIn = 9,
XI_FocusOut = 10,
XI_HierarchyChanged = 11,
XI_PropertyEvent = 12,
XI_RawKeyPress = 13,
XI_RawKeyRelease = 14,
XI_RawButtonPress = 15,
XI_RawButtonRelease = 16,
XI_RawMotion = 17,
XI_TouchBegin = 18 /* XI 2.2 */,
XI_TouchUpdate = 19,
XI_TouchEnd = 20,
XI_TouchOwnership = 21,
XI_RawTouchBegin = 22,
XI_RawTouchUpdate = 23,
XI_RawTouchEnd = 24,
XI_BarrierHit = 25 /* XI 2.3 */,
XI_BarrierLeave = 26,
XI_LASTEVENT = XI_BarrierLeave,
}
}

632
src/Avalonia.X11/XLib.cs

@ -0,0 +1,632 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable CommentTypo
// ReSharper disable UnusedMember.Global
// ReSharper disable IdentifierTypo
// ReSharper disable NotAccessedField.Global
// ReSharper disable UnusedMethodReturnValue.Global
namespace Avalonia.X11
{
internal unsafe static class XLib
{
const string libX11 = "libX11.so.6";
const string libX11Randr = "libXrandr.so.2";
const string libX11Ext = "libXext.so.6";
const string libXInput = "libXi.so.6";
[DllImport(libX11)]
public static extern IntPtr XOpenDisplay(IntPtr display);
[DllImport(libX11)]
public static extern int XCloseDisplay(IntPtr display);
[DllImport(libX11)]
public static extern IntPtr XSynchronize(IntPtr display, bool onoff);
[DllImport(libX11)]
public static extern IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height,
int border_width, int depth, int xclass, IntPtr visual, UIntPtr valuemask,
ref XSetWindowAttributes attributes);
[DllImport(libX11)]
public static extern IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, int width,
int height, int border_width, IntPtr border, IntPtr background);
[DllImport(libX11)]
public static extern int XMapWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XUnmapWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XMapSubindows(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XUnmapSubwindows(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern IntPtr XRootWindow(IntPtr display, int screen_number);
[DllImport(libX11)]
public static extern IntPtr XDefaultRootWindow(IntPtr display);
[DllImport(libX11)]
public static extern IntPtr XNextEvent(IntPtr display, out XEvent xevent);
[DllImport(libX11)]
public static extern int XConnectionNumber(IntPtr diplay);
[DllImport(libX11)]
public static extern int XPending(IntPtr diplay);
[DllImport(libX11)]
public static extern IntPtr XSelectInput(IntPtr display, IntPtr window, IntPtr mask);
[DllImport(libX11)]
public static extern int XDestroyWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XReparentWindow(IntPtr display, IntPtr window, IntPtr parent, int x, int y);
[DllImport(libX11)]
public static extern int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, int width, int height);
[DllImport(libX11)]
public static extern int XResizeWindow(IntPtr display, IntPtr window, int width, int height);
[DllImport(libX11)]
public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes);
[DllImport(libX11)]
public static extern int XFlush(IntPtr display);
[DllImport(libX11)]
public static extern int XSetWMName(IntPtr display, IntPtr window, ref XTextProperty text_prop);
[DllImport(libX11)]
public static extern int XStoreName(IntPtr display, IntPtr window, string window_name);
[DllImport(libX11)]
public static extern int XFetchName(IntPtr display, IntPtr window, ref IntPtr window_name);
[DllImport(libX11)]
public static extern int XSendEvent(IntPtr display, IntPtr window, bool propagate, IntPtr event_mask,
ref XEvent send_event);
[DllImport(libX11)]
public static extern int XQueryTree(IntPtr display, IntPtr window, out IntPtr root_return,
out IntPtr parent_return, out IntPtr children_return, out int nchildren_return);
[DllImport(libX11)]
public static extern int XFree(IntPtr data);
[DllImport(libX11)]
public static extern int XRaiseWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern uint XLowerWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern uint XConfigureWindow(IntPtr display, IntPtr window, ChangeWindowFlags value_mask,
ref XWindowChanges values);
public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, PixelSize size)
=> XConfigureResizeWindow(display, window, size.Width, size.Height);
public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, int width, int height)
{
var changes = new XWindowChanges
{
width = width,
height = height
};
return XConfigureWindow(display, window, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth,
ref changes);
}
[DllImport(libX11)]
public static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists);
[DllImport(libX11)]
public static extern int XInternAtoms(IntPtr display, string[] atom_names, int atom_count, bool only_if_exists,
IntPtr[] atoms);
[DllImport(libX11)]
public static extern IntPtr XGetAtomName(IntPtr display, IntPtr atom);
public static string GetAtomName(IntPtr display, IntPtr atom)
{
var ptr = XGetAtomName(display, atom);
if (ptr == IntPtr.Zero)
return null;
var s = Marshal.PtrToStringAnsi(ptr);
XFree(ptr);
return s;
}
[DllImport(libX11)]
public static extern int XSetWMProtocols(IntPtr display, IntPtr window, IntPtr[] protocols, int count);
[DllImport(libX11)]
public static extern int XGrabPointer(IntPtr display, IntPtr window, bool owner_events, EventMask event_mask,
GrabMode pointer_mode, GrabMode keyboard_mode, IntPtr confine_to, IntPtr cursor, IntPtr timestamp);
[DllImport(libX11)]
public static extern int XUngrabPointer(IntPtr display, IntPtr timestamp);
[DllImport(libX11)]
public static extern bool XQueryPointer(IntPtr display, IntPtr window, out IntPtr root, out IntPtr child,
out int root_x, out int root_y, out int win_x, out int win_y, out int keys_buttons);
[DllImport(libX11)]
public static extern bool XTranslateCoordinates(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x,
int src_y, out int intdest_x_return, out int dest_y_return, out IntPtr child_return);
[DllImport(libX11)]
public static extern bool XGetGeometry(IntPtr display, IntPtr window, out IntPtr root, out int x, out int y,
out int width, out int height, out int border_width, out int depth);
[DllImport(libX11)]
public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y,
out int width, out int height, IntPtr border_width, IntPtr depth);
[DllImport(libX11)]
public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y,
IntPtr width, IntPtr height, IntPtr border_width, IntPtr depth);
[DllImport(libX11)]
public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, IntPtr x, IntPtr y,
out int width, out int height, IntPtr border_width, IntPtr depth);
[DllImport(libX11)]
public static extern uint XWarpPointer(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, int src_y,
uint src_width, uint src_height, int dest_x, int dest_y);
[DllImport(libX11)]
public static extern int XClearWindow(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XClearArea(IntPtr display, IntPtr window, int x, int y, int width, int height,
bool exposures);
// Colormaps
[DllImport(libX11)]
public static extern IntPtr XDefaultScreenOfDisplay(IntPtr display);
[DllImport(libX11)]
public static extern int XScreenNumberOfScreen(IntPtr display, IntPtr Screen);
[DllImport(libX11)]
public static extern IntPtr XDefaultVisual(IntPtr display, int screen_number);
[DllImport(libX11)]
public static extern uint XDefaultDepth(IntPtr display, int screen_number);
[DllImport(libX11)]
public static extern int XDefaultScreen(IntPtr display);
[DllImport(libX11)]
public static extern IntPtr XDefaultColormap(IntPtr display, int screen_number);
[DllImport(libX11)]
public static extern int XLookupColor(IntPtr display, IntPtr Colormap, string Coloranem,
ref XColor exact_def_color, ref XColor screen_def_color);
[DllImport(libX11)]
public static extern int XAllocColor(IntPtr display, IntPtr Colormap, ref XColor colorcell_def);
[DllImport(libX11)]
public static extern int XSetTransientForHint(IntPtr display, IntPtr window, IntPtr parent);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, ref MotifWmHints data, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, ref uint value, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, ref IntPtr value, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, uint[] data, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, int[] data, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, IntPtr[] data, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, void* data, int nelements);
[DllImport(libX11)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, IntPtr atoms, int nelements);
[DllImport(libX11, CharSet = CharSet.Ansi)]
public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type,
int format, PropertyMode mode, string text, int text_length);
[DllImport(libX11)]
public static extern int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property);
// Drawing
[DllImport(libX11)]
public static extern IntPtr XCreateGC(IntPtr display, IntPtr window, IntPtr valuemask, ref XGCValues values);
[DllImport(libX11)]
public static extern int XFreeGC(IntPtr display, IntPtr gc);
[DllImport(libX11)]
public static extern int XSetFunction(IntPtr display, IntPtr gc, GXFunction function);
[DllImport(libX11)]
internal static extern int XSetLineAttributes(IntPtr display, IntPtr gc, int line_width, GCLineStyle line_style,
GCCapStyle cap_style, GCJoinStyle join_style);
[DllImport(libX11)]
public static extern int XDrawLine(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int x2, int y2);
[DllImport(libX11)]
public static extern int XDrawRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width,
int height);
[DllImport(libX11)]
public static extern int XFillRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width,
int height);
[DllImport(libX11)]
public static extern int XSetWindowBackground(IntPtr display, IntPtr window, IntPtr background);
[DllImport(libX11)]
public static extern int XCopyArea(IntPtr display, IntPtr src, IntPtr dest, IntPtr gc, int src_x, int src_y,
int width, int height, int dest_x, int dest_y);
[DllImport(libX11)]
public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset,
IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format,
out IntPtr nitems, out IntPtr bytes_after, out IntPtr prop);
[DllImport(libX11)]
public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time);
[DllImport(libX11)]
public static extern int XIconifyWindow(IntPtr display, IntPtr window, int screen_number);
[DllImport(libX11)]
public static extern int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor);
[DllImport(libX11)]
public static extern int XUndefineCursor(IntPtr display, IntPtr window);
[DllImport(libX11)]
public static extern int XFreeCursor(IntPtr display, IntPtr cursor);
[DllImport(libX11)]
public static extern IntPtr XCreateFontCursor(IntPtr display, CursorFontShape shape);
[DllImport(libX11)]
public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask,
ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot);
[DllImport(libX11)]
public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width,
int height, IntPtr fg, IntPtr bg, int depth);
[DllImport(libX11)]
public static extern IntPtr XCreatePixmap(IntPtr display, IntPtr d, int width, int height, int depth);
[DllImport(libX11)]
public static extern IntPtr XFreePixmap(IntPtr display, IntPtr pixmap);
[DllImport(libX11)]
public static extern int XQueryBestCursor(IntPtr display, IntPtr drawable, int width, int height,
out int best_width, out int best_height);
[DllImport(libX11)]
public static extern IntPtr XWhitePixel(IntPtr display, int screen_no);
[DllImport(libX11)]
public static extern IntPtr XBlackPixel(IntPtr display, int screen_no);
[DllImport(libX11)]
public static extern void XGrabServer(IntPtr display);
[DllImport(libX11)]
public static extern void XUngrabServer(IntPtr display);
[DllImport(libX11)]
public static extern void XGetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints,
out IntPtr supplied_return);
[DllImport(libX11)]
public static extern void XSetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints);
[DllImport(libX11)]
public static extern void XSetZoomHints(IntPtr display, IntPtr window, ref XSizeHints hints);
[DllImport(libX11)]
public static extern void XSetWMHints(IntPtr display, IntPtr window, ref XWMHints wmhints);
[DllImport(libX11)]
public static extern int XGetIconSizes(IntPtr display, IntPtr window, out IntPtr size_list, out int count);
[DllImport(libX11)]
public static extern IntPtr XSetErrorHandler(XErrorHandler error_handler);
[DllImport(libX11)]
public static extern IntPtr XGetErrorText(IntPtr display, byte code, StringBuilder buffer, int length);
[DllImport(libX11)]
public static extern int XInitThreads();
[DllImport(libX11)]
public static extern int XConvertSelection(IntPtr display, IntPtr selection, IntPtr target, IntPtr property,
IntPtr requestor, IntPtr time);
[DllImport(libX11)]
public static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection);
[DllImport(libX11)]
public static extern int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, IntPtr time);
[DllImport(libX11)]
public static extern int XSetPlaneMask(IntPtr display, IntPtr gc, IntPtr mask);
[DllImport(libX11)]
public static extern int XSetForeground(IntPtr display, IntPtr gc, UIntPtr foreground);
[DllImport(libX11)]
public static extern int XSetBackground(IntPtr display, IntPtr gc, UIntPtr background);
[DllImport(libX11)]
public static extern int XBell(IntPtr display, int percent);
[DllImport(libX11)]
public static extern int XChangeActivePointerGrab(IntPtr display, EventMask event_mask, IntPtr cursor,
IntPtr time);
[DllImport(libX11)]
public static extern bool XFilterEvent(ref XEvent xevent, IntPtr window);
[DllImport(libX11)]
public static extern void XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, IntPtr supported);
[DllImport(libX11)]
public static extern void XPeekEvent(IntPtr display, out XEvent xevent);
[DllImport(libX11)]
public static extern void XMatchVisualInfo(IntPtr display, int screen, int depth, int klass, out XVisualInfo info);
[DllImport(libX11)]
public static extern IntPtr XLockDisplay(IntPtr display);
[DllImport(libX11)]
public static extern IntPtr XUnlockDisplay(IntPtr display);
[DllImport(libX11)]
public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values);
[DllImport(libX11)]
public static extern int XInitImage(ref XImage image);
[DllImport(libX11)]
public static extern int XDestroyImage(ref XImage image);
[DllImport(libX11)]
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image,
int srcx, int srcy, int destx, int desty, uint width, uint height);
[DllImport(libX11)]
public static extern int XSync(IntPtr display, bool discard);
[DllImport(libX11)]
public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create);
public enum XLookupStatus
{
XBufferOverflow = -1,
XLookupNone = 1,
XLookupChars = 2,
XLookupKeySym = 3,
XLookupBoth = 4
}
[DllImport (libX11)]
public static extern unsafe int XLookupString(ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
[DllImport (libX11)]
public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
[DllImport (libX11)]
public static extern unsafe IntPtr XKeycodeToKeysym(IntPtr display, int keycode, int index);
[DllImport (libX11)]
public static extern unsafe IntPtr XSetLocaleModifiers(string modifiers);
[DllImport (libX11)]
public static extern IntPtr XOpenIM (IntPtr display, IntPtr rdb, IntPtr res_name, IntPtr res_class);
[DllImport (libX11)]
public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, IntPtr terminator);
[DllImport (libX11)]
public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator);
[DllImport (libX11)]
public static extern void XCloseIM (IntPtr xim);
[DllImport (libX11)]
public static extern void XDestroyIC (IntPtr xic);
[DllImport(libX11)]
public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string name,
out int majorOpcode, out int firstEvent, out int firstError);
[DllImport(libX11)]
public static extern bool XGetEventData(IntPtr display, void* cookie);
[DllImport(libX11)]
public static extern void XFreeEventData(IntPtr display, void* cookie);
[DllImport(libX11Randr)]
public static extern int XRRQueryExtension (IntPtr dpy,
out int event_base_return,
out int error_base_return);
[DllImport(libX11Randr)]
public static extern int XRRQueryVersion(IntPtr dpy,
out int major_version_return,
out int minor_version_return);
[DllImport(libX11Randr)]
public static extern XRRMonitorInfo*
XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors);
[DllImport(libX11Randr)]
public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);
[DllImport(libXInput)]
public static extern Status XIQueryVersion(IntPtr dpy, ref int major, ref int minor);
[DllImport(libXInput)]
public static extern IntPtr XIQueryDevice(IntPtr dpy, int deviceid, out int ndevices_return);
[DllImport(libXInput)]
public static extern void XIFreeDeviceInfo(XIDeviceInfo* info);
public static void XISetMask(ref int mask, XiEventType ev)
{
mask |= (1 << (int)ev);
}
public static int XiEventMaskLen { get; } = 4;
public static bool XIMaskIsSet(void* ptr, int shift) =>
(((byte*)(ptr))[(shift) >> 3] & (1 << (shift & 7))) != 0;
[DllImport(libXInput)]
public static extern Status XISelectEvents(
IntPtr dpy,
IntPtr win,
XIEventMask* masks,
int num_masks
);
public static Status XiSelectEvents(IntPtr display, IntPtr window, Dictionary<int, List<XiEventType>> devices)
{
var masks = stackalloc int[devices.Count];
var emasks = stackalloc XIEventMask[devices.Count];
int c = 0;
foreach (var d in devices)
{
foreach (var ev in d.Value)
XISetMask(ref masks[c], ev);
emasks[c] = new XIEventMask
{
Mask = &masks[c],
Deviceid = d.Key,
MaskLen = XiEventMaskLen
};
c++;
}
return XISelectEvents(display, window, emasks, devices.Count);
}
public struct XGeometry
{
public IntPtr root;
public int x;
public int y;
public int width;
public int height;
public int bw;
public int d;
}
public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo)
{
geo = new XGeometry();
return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height,
out geo.bw, out geo.d);
}
public static void QueryPointer (IntPtr display, IntPtr w, out IntPtr root, out IntPtr child,
out int root_x, out int root_y, out int child_x, out int child_y,
out int mask)
{
IntPtr c;
XGrabServer (display);
XQueryPointer(display, w, out root, out c,
out root_x, out root_y, out child_x, out child_y,
out mask);
if (root != w)
c = root;
IntPtr child_last = IntPtr.Zero;
while (c != IntPtr.Zero) {
child_last = c;
XQueryPointer(display, c, out root, out c,
out root_x, out root_y, out child_x, out child_y,
out mask);
}
XUngrabServer (display);
XFlush (display);
child = child_last;
}
public static (int x, int y) GetCursorPos(X11Info x11, IntPtr? handle = null)
{
IntPtr root;
IntPtr child;
int root_x;
int root_y;
int win_x;
int win_y;
int keys_buttons;
QueryPointer(x11.Display, handle ?? x11.RootWindow, out root, out child, out root_x, out root_y, out win_x, out win_y,
out keys_buttons);
if (handle != null)
{
return (win_x, win_y);
}
else
{
return (root_x, root_y);
}
}
public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, Action<XEvent> handler)
{
var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow,
0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);
plat.Windows[win] = handler;
return win;
}
}
}

115
src/Gtk/Avalonia.Gtk3/Gtk3ForeignX11SystemDialog.cs

@ -0,0 +1,115 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3.Interop;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.Gtk3
{
public class Gtk3ForeignX11SystemDialog : ISystemDialogImpl
{
private Task<bool> _initialized;
private SystemDialogBase _inner = new SystemDialogBase();
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
var xid = parent.Handle.Handle;
return await await RunOnGtkThread(
() => _inner.ShowFileDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid)));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
var xid = parent.Handle.Handle;
return await await RunOnGtkThread(
() => _inner.ShowFolderDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid)));
}
void UpdateParent(GtkFileChooser chooser, IntPtr xid)
{
Native.GtkWidgetRealize(chooser);
var window = Native.GtkWidgetGetWindow(chooser);
var parent = Native.GdkWindowForeignNewForDisplay(GdkDisplay, xid);
if (window != IntPtr.Zero && parent != IntPtr.Zero)
Native.GdkWindowSetTransientFor(window, parent);
}
async Task EnsureInitialized()
{
if (_initialized == null)
{
var tcs = new TaskCompletionSource<bool>();
_initialized = tcs.Task;
new Thread(() => GtkThread(tcs))
{
IsBackground = true
}.Start();
}
if (!(await _initialized))
throw new Exception("Unable to initialize GTK on separate thread");
}
Task<T> RunOnGtkThread<T>(Func<T> action)
{
var tcs = new TaskCompletionSource<T>();
GlibTimeout.Add(0, 0, () =>
{
try
{
tcs.SetResult(action());
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return false;
});
return tcs.Task;
}
void GtkThread(TaskCompletionSource<bool> tcs)
{
try
{
X11.XInitThreads();
}catch{}
Resolver.Resolve();
if (Native.GdkWindowForeignNewForDisplay == null)
throw new Exception("gdk_x11_window_foreign_new_for_display is not found in your libgdk-3.so");
using (var backends = new Utf8Buffer("x11"))
Native.GdkSetAllowedBackends?.Invoke(backends);
if (!Native.GtkInitCheck(0, IntPtr.Zero))
{
tcs.SetResult(false);
return;
}
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}"))
App = Native.GtkApplicationNew(utf, 0);
if (App == IntPtr.Zero)
{
tcs.SetResult(false);
return;
}
GdkDisplay = Native.GdkGetDefaultDisplay();
tcs.SetResult(true);
while (true)
Native.GtkMainIteration();
}
private IntPtr GdkDisplay { get; set; }
private IntPtr App { get; set; }
}
}

13
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -50,6 +50,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_init(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate bool gtk_init_check(int argc, IntPtr argv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_set_allowed_backends (Utf8Buffer backends);
@ -288,6 +291,12 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_end_paint(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk, optional: true)]
public delegate IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_event_request_motions(IntPtr ev);
@ -414,6 +423,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_set_transient_for GtkWindowSetTransientFor;
public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
public static D.gtk_init GtkInit;
public static D.gtk_init_check GtkInitCheck;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;
public static D.gtk_widget_show GtkWidgetShow;
@ -483,8 +493,9 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_process_updates GdkWindowProcessUpdates;
public static D.gdk_window_begin_paint_rect GdkWindowBeginPaintRect;
public static D.gdk_window_end_paint GdkWindowEndPaint;
public static D.gdk_x11_window_foreign_new_for_display GdkWindowForeignNewForDisplay;
public static D.gdk_window_set_transient_for GdkWindowSetTransientFor;
public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile;
public static D.gtk_icon_theme_get_default GtkIconThemeGetDefault;
public static D.gtk_icon_theme_load_icon GtkIconThemeLoadIcon;

36
src/Gtk/Avalonia.Gtk3/SystemDialogs.cs

@ -11,16 +11,17 @@ using Avalonia.Platform.Interop;
namespace Avalonia.Gtk3
{
class SystemDialog : ISystemDialogImpl
class SystemDialogBase
{
unsafe static Task<string[]> ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
bool multiselect, string initialFileName)
public unsafe static Task<string[]> ShowDialog(string title, GtkWindow parent, GtkFileChooserAction action,
bool multiselect, string initialFileName, Action<GtkFileChooser> modify)
{
GtkFileChooser dlg;
parent = parent ?? GtkWindow.Null;
using (var name = new Utf8Buffer(title))
dlg = Native.GtkFileChooserDialogNew(name, parent, action, IntPtr.Zero);
modify?.Invoke(dlg);
if (multiselect)
Native.GtkFileChooserSetSelectMultiple(dlg, true);
@ -42,7 +43,7 @@ namespace Avalonia.Gtk3
dispose();
return false;
}),
Signal.Connect<Native.D.signal_dialog_response>(dlg, "response", (_, resp, __)=>
Signal.Connect<Native.D.signal_dialog_response>(dlg, "response", (_, resp, __) =>
{
string[] result = null;
if (resp == GtkResponseType.Accept)
@ -56,9 +57,11 @@ namespace Avalonia.Gtk3
rlst.Add(Utf8Buffer.StringFromPtr(cgs->Data));
cgs = cgs->Next;
}
Native.GSlistFree(gs);
result = rlst.ToArray();
}
Native.GtkWidgetHide(dlg);
dispose();
tcs.TrySetResult(result);
@ -70,27 +73,38 @@ namespace Avalonia.Gtk3
Native.GtkDialogAddButton(dlg, open, GtkResponseType.Accept);
using (var open = new Utf8Buffer("Cancel"))
Native.GtkDialogAddButton(dlg, open, GtkResponseType.Cancel);
if(initialFileName!=null)
if (initialFileName != null)
using (var fn = new Utf8Buffer(initialFileName))
Native.GtkFileChooserSetFilename(dlg, fn);
Native.GtkWindowPresent(dlg);
return tcs.Task;
}
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, GtkWindow parent,
Action<GtkFileChooser> modify = null)
{
return ShowDialog(dialog.Title, ((WindowBaseImpl)parent)?.GtkWidget,
return ShowDialog(dialog.Title, parent,
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName));
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName), modify);
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, GtkWindow parent,
Action<GtkFileChooser> modify = null)
{
var res = await ShowDialog(dialog.Title, ((WindowBaseImpl) parent)?.GtkWidget,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
var res = await ShowDialog(dialog.Title, parent,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory, modify);
return res?.FirstOrDefault();
}
}
class SystemDialog : SystemDialogBase, ISystemDialogImpl
{
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
=> ShowFolderDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
=> ShowFileDialogAsync(dialog, ((WindowBaseImpl)parent)?.GtkWidget);
}
}

29
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -20,7 +21,7 @@ namespace Avalonia.Skia
/// </summary>
public class DrawingContextImpl : IDrawingContextImpl
{
private readonly IDisposable[] _disposables;
private IDisposable[] _disposables;
private readonly Vector _dpi;
private readonly Stack<PaintWrapper> _maskStack = new Stack<PaintWrapper>();
private readonly Stack<double> _opacityStack = new Stack<double>();
@ -30,7 +31,7 @@ namespace Avalonia.Skia
private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform;
private GRContext _grContext;
private bool _disposed;
/// <summary>
/// Context create info.
/// </summary>
@ -74,6 +75,8 @@ namespace Avalonia.Skia
_disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext;
if (_grContext != null)
Monitor.Enter(_grContext);
Canvas = createInfo.Canvas;
@ -257,14 +260,26 @@ namespace Avalonia.Skia
/// <inheritdoc />
public virtual void Dispose()
{
if (_disposables == null)
{
if(_disposed)
return;
}
try
{
if (_grContext != null)
{
Monitor.Exit(_grContext);
_grContext = null;
}
foreach (var disposable in _disposables)
if (_disposables != null)
{
foreach (var disposable in _disposables)
disposable?.Dispose();
_disposables = null;
}
}
finally
{
disposable?.Dispose();
_disposed = true;
}
}

2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -272,7 +272,7 @@ namespace Avalonia.Skia
}
var textLine = Text.Substring(line.Start, line.Length);
currX -= paint.MeasureText(textLine) * factor;
currX -= textLine.Length == 0 ? 0 : paint.MeasureText(textLine) * factor;
for (int i = line.Start; i < line.Start + line.Length;)
{

8
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -61,7 +61,7 @@ namespace Avalonia.Skia
DisableTextLcdRendering = true
};
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, framebuffer);
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);
}
/// <summary>
@ -119,11 +119,7 @@ namespace Avalonia.Skia
_conversionShim = null;
_preFramebufferCopyHandler = null;
if (_conversionShim != null)
{
_framebufferSurface?.Dispose();
}
_framebufferSurface?.Dispose();
_framebufferSurface = null;
_currentFramebufferAddress = IntPtr.Zero;
}

56
src/Skia/Avalonia.Skia/GlRenderTarget.cs

@ -30,36 +30,46 @@ namespace Avalonia.Skia
var size = session.Size;
var scaling = session.Scaling;
if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
{
throw new InvalidOperationException(
$"Can't create drawing context for surface with {size} size and {scaling} scaling");
}
gl.Viewport(0, 0, size.Width, size.Height);
gl.ClearStencil(0);
gl.ClearColor(0, 0, 0, 0);
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_grContext.ResetContext();
GRBackendRenderTarget renderTarget =
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat()));
var surface = SKSurface.Create(_grContext, renderTarget,
GRSurfaceOrigin.BottomLeft,
GRPixelConfig.Rgba8888.ToColorType());
var nfo = new DrawingContextImpl.CreateInfo
lock (_grContext)
{
GrContext = _grContext,
Canvas = surface.Canvas,
Dpi = SkiaPlatform.DefaultDpi * scaling,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
};
_grContext.ResetContext();
return new DrawingContextImpl(nfo, Disposable.Create(() =>
{
surface.Canvas.Flush();
surface.Dispose();
renderTarget.Dispose();
session.Dispose();
}));
GRBackendRenderTarget renderTarget =
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat()));
var surface = SKSurface.Create(_grContext, renderTarget,
GRSurfaceOrigin.BottomLeft,
GRPixelConfig.Rgba8888.ToColorType());
var nfo = new DrawingContextImpl.CreateInfo
{
GrContext = _grContext,
Canvas = surface.Canvas,
Dpi = SkiaPlatform.DefaultDpi * scaling,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
};
return new DrawingContextImpl(nfo, Disposable.Create(() =>
{
surface.Canvas.Flush();
surface.Dispose();
renderTarget.Dispose();
_grContext.Flush();
session.Dispose();
}));
}
}
}
}

3
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -24,7 +24,8 @@ namespace Avalonia.Skia
{
using (var skiaStream = new SKManagedStream(stream))
{
_image = SKImage.FromEncodedData(SKData.Create(skiaStream));
using (var data = SKData.Create(skiaStream))
_image = SKImage.FromEncodedData(data);
if (_image == null)
{

10
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -27,11 +27,13 @@ namespace Avalonia.Skia
if (gl != null)
{
var display = gl.ImmediateContext.Display;
var iface = display.Type == GlDisplayType.OpenGL2
using (var iface = display.Type == GlDisplayType.OpenGL2
? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc));
gl.ImmediateContext.MakeCurrent();
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
gl.ImmediateContext.MakeCurrent();
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
}
}
}

Loading…
Cancel
Save