Browse Source

Merge branch 'master' into fix-tooltip-null-values

pull/5297/head
Max Katz 5 years ago
committed by GitHub
parent
commit
e7ce75ab64
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      Avalonia.sln.DotSettings
  2. 2
      build/AndroidWorkarounds.props
  3. 2
      build/ApiDiff.props
  4. 4
      build/HarfBuzzSharp.props
  5. 2
      build/SharedVersion.props
  6. 23
      build/SourceLink.props
  7. 12
      packages/Avalonia/Avalonia.csproj
  8. 2
      readme.md
  9. 14
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  10. 21
      samples/ControlCatalog.Android/MainActivity.cs
  11. 2
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  12. 59
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  13. BIN
      samples/ControlCatalog.Android/Resources/drawable/Icon.png
  14. 13
      samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
  15. 13
      samples/ControlCatalog.Android/Resources/layout/Main.axml
  16. 5
      samples/ControlCatalog.Android/Resources/values/Strings.xml
  17. 4
      samples/ControlCatalog.Android/Resources/values/colors.xml
  18. 17
      samples/ControlCatalog.Android/Resources/values/styles.xml
  19. 32
      samples/ControlCatalog.Android/SplashActivity.cs
  20. 3
      samples/ControlCatalog.NetCore/Program.cs
  21. BIN
      samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf
  22. 3
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  23. 19
      src/Android/Avalonia.Android/AndroidPlatform.cs
  24. 32
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  25. 23
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  26. 4
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  27. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  28. 41
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  29. 11
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  30. 60
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  31. 5
      src/Android/Avalonia.Android/SystemDialogImpl.cs
  32. 4
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  33. 4
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  34. 2
      src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
  35. 17
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  36. 3
      src/Avalonia.Base/Avalonia.Base.csproj
  37. 25
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  38. 8
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  39. 35
      src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
  40. 8
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  41. 11
      src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs
  42. 25
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  43. 6
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  44. 1
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  45. 7
      src/Avalonia.Controls/TextBox.cs
  46. 41
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  47. 6
      src/Avalonia.Controls/TopLevel.cs
  48. 8
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  49. 9
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  50. 110
      src/Avalonia.FreeDesktop/DBusCallQueue.cs
  51. 11
      src/Avalonia.FreeDesktop/DBusHelper.cs
  52. 288
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  53. 69
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
  54. 67
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs
  55. 51
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
  56. 149
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  57. 52
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs
  58. 45
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs
  59. 105
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  60. 53
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  61. 31
      src/Avalonia.FreeDesktop/IX11InputMethod.cs
  62. 35
      src/Avalonia.Input/InputElement.cs
  63. 6
      src/Avalonia.Input/KeyboardDevice.cs
  64. 60
      src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
  65. 15
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  66. 101
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  67. 12
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  68. 12
      src/Avalonia.Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
  69. 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  70. 109
      src/Avalonia.Input/TextInput/TransformTrackingHelper.cs
  71. 4
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  72. 2
      src/Avalonia.OpenGL/GlInterface.cs
  73. 2
      src/Avalonia.Themes.Default/CaptionButtons.xaml
  74. 2
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  75. 4
      src/Avalonia.X11/X11Clipboard.cs
  76. 4
      src/Avalonia.X11/X11Globals.cs
  77. 25
      src/Avalonia.X11/X11Info.cs
  78. 72
      src/Avalonia.X11/X11Platform.cs
  79. 11
      src/Avalonia.X11/X11PlatformThreading.cs
  80. 2
      src/Avalonia.X11/X11Screens.cs
  81. 130
      src/Avalonia.X11/X11Structs.cs
  82. 208
      src/Avalonia.X11/X11Window.Ime.cs
  83. 121
      src/Avalonia.X11/X11Window.Xim.cs
  84. 80
      src/Avalonia.X11/X11Window.cs
  85. 66
      src/Avalonia.X11/XLib.cs
  86. 14
      src/Shared/PlatformSupport/DynLoader.cs
  87. 20
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  88. 11
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  89. 8
      src/Windows/Avalonia.Win32/Win32GlManager.cs
  90. 2
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
  91. 2
      tests/Avalonia.Markup.Xaml.UnitTests/StyleIncludeTests.cs

3
Avalonia.sln.DotSettings

@ -38,4 +38,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

2
build/AndroidWorkarounds.props

@ -2,7 +2,7 @@
<ItemGroup Condition="'$(AndroidApplication)' == 'true'">
<!-- WORKAROUND: The packages below are transitively referenced by System.Memory,
but Xamarin.Android applications need the newest versions directly referenced for the linker. -->
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
</ItemGroup>
<Target Name="_RemoveNonExistingResgenFile" BeforeTargets="CoreCompile" Condition="'$(_SdkSetAndroidResgenFile)' == 'true' And '$(AndroidResgenFile)' != '' And !Exists('$(AndroidResgenFile)')">

2
build/ApiDiff.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApiContractPackageVersion>0.10.0-rc1</ApiContractPackageVersion>
<ApiContractPackageVersion>0.10.0</ApiContractPackageVersion>
<NugetPackageName Condition="'$(PackageId)' != ''">$(PackageId)</NugetPackageName>
<NugetPackageName Condition="'$(PackageId)' == ''">Avalonia</NugetPackageName>
</PropertyGroup>

4
build/HarfBuzzSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.6" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.6" />
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.7" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.7" />
</ItemGroup>
</Project>

2
build/SharedVersion.props

@ -3,7 +3,7 @@
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.10.999</Version>
<Copyright>Copyright 2020 &#169; The AvaloniaUI Project</Copyright>
<Copyright>Copyright 2021 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

23
build/SourceLink.props

@ -1,5 +1,26 @@
<Project>
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>false</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
<!-- Workaround for https://github.com/dotnet/sdk/issues/11105 -->
<ItemGroup>
<SourceRoot Include="$(NuGetPackageRoot)" Condition="'$(NuGetPackageRoot)' != ''" />
</ItemGroup>
</Project>

12
packages/Avalonia/Avalonia.csproj

@ -6,7 +6,9 @@
<ItemGroup>
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" >
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
@ -29,21 +31,23 @@
</_PackageFiles>
</ItemGroup>
</Target>
<ItemGroup>
<Content Include="*.props">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="*.targets">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="AvaloniaItemSchema.xaml">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
</ItemGroup>
<Import Project="..\..\build\SharedVersion.props" />
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\CoreLibraries.props" />
<Import Project="..\..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
</Project>

2
readme.md

@ -12,8 +12,6 @@ Avalonia is a cross-platform XAML-based UI framework providing a flexible stylin
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
> **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info.
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
## 🚀 Getting Started

14
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -71,21 +71,23 @@
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SplashActivity.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Main.axml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\Strings.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />

21
samples/ControlCatalog.Android/MainActivity.cs

@ -1,31 +1,18 @@
using System;
using Android.App;
using Android.App;
using Android.OS;
using Android.Content.PM;
using Avalonia.Android;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
public class MainActivity : AvaloniaActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
Content = new MainView();
}
base.OnCreate(savedInstanceState);
Content = new MainView();
}
}
}

2
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk />
<uses-sdk android:targetSdkVersion="29" />
<application android:label="ControlCatalog.Android"></application>
</manifest>

59
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -15,7 +14,7 @@ namespace ControlCatalog.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@ -26,8 +25,6 @@ namespace ControlCatalog.Android
public static void UpdateIdValues()
{
global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello;
}
public partial class Attribute
@ -43,69 +40,59 @@ namespace ControlCatalog.Android
}
}
public partial class Drawable
public partial class Color
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
// aapt resource value: 0x7F010000
public const int splash_background = 2130771968;
static Drawable()
static Color()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Drawable()
private Color()
{
}
}
public partial class Id
public partial class Drawable
{
// aapt resource value: 0x7f050000
public const int MyButton = 2131034112;
static Id()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Id()
{
}
}
public partial class Layout
{
// aapt resource value: 0x7F020000
public const int Icon = 2130837504;
// aapt resource value: 0x7f030000
public const int Main = 2130903040;
// aapt resource value: 0x7F020001
public const int splash_screen = 2130837505;
static Layout()
static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
private Drawable()
{
}
}
public partial class String
public partial class Style
{
// aapt resource value: 0x7f040001
public const int ApplicationName = 2130968577;
// aapt resource value: 0x7F030000
public const int MyTheme = 2130903040;
// aapt resource value: 0x7F030001
public const int MyTheme_NoActionBar = 2130903041;
// aapt resource value: 0x7f040000
public const int Hello = 2130968576;
// aapt resource value: 0x7F030002
public const int MyTheme_Splash = 2130903042;
static String()
static Style()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
private Style()
{
}
}

BIN
samples/ControlCatalog.Android/Resources/drawable/Icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

13
samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/splash_background"/>
</item>
<item android:drawable="@drawable/icon"
android:width="120dp"
android:height="120dp"
android:gravity="center" />
</layer-list>

13
samples/ControlCatalog.Android/Resources/layout/Main.axml

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/MyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/Hello"
/>
</LinearLayout>

5
samples/ControlCatalog.Android/Resources/values/Strings.xml

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Hello">Hello World, Click Me!</string>
<string name="ApplicationName">ControlCatalog.Android</string>
</resources>

4
samples/ControlCatalog.Android/Resources/values/colors.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#FFFFFF</color>
</resources>

17
samples/ControlCatalog.Android/Resources/values/styles.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyTheme">
</style>
<style name="MyTheme.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources>

32
samples/ControlCatalog.Android/SplashActivity.cs

@ -0,0 +1,32 @@
using Android.App;
using Android.Content;
using Android.OS;
using Application = Android.App.Application;
using Avalonia;
namespace ControlCatalog.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected override void OnResume()
{
base.OnResume();
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
}
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
}
}

3
samples/ControlCatalog.NetCore/Program.cs

@ -109,7 +109,8 @@ namespace ControlCatalog.NetCore
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
UseDBusMenu = true
UseDBusMenu = true,
EnableIme = true,
})
.With(new Win32PlatformOptions
{

BIN
samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf

Binary file not shown.

3
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -64,5 +64,8 @@
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel>
</StackPanel>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="200" MaxWidth="400"
FontFamily="avares://ControlCatalog/Assets/Fonts#WenQuanYi Micro Hei"
Text="计算机科学(是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究。计算机科学包含很多分支领域;有些强调特定结果的计算,比如计算机图形学;而有些是探討计算问题的性质,比如计算复杂性理论;还有一些领域專注于怎样实现计算,比如程式語言理論是研究描述计算的方法,而程式设计是应用特定的程式語言解决特定的计算问题,人机交互则是專注于怎样使计算机和计算变得有用、好用,以及随时随地为人所用。&#xD;&#xD;有时公众会误以为计算机科学就是解决计算机问题的事业(比如信息技术),或者只是与使用计算机的经验有关,如玩游戏、上网或者文字处理。其实计算机科学所关注的,不仅仅是去理解实现类似游戏、浏览器这些软件的程序的性质,更要通过现有的知识创造新的程序或者改进已有的程序。" />
</StackPanel>
</UserControl>

19
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,11 +1,13 @@
using System;
using Avalonia.Android;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
@ -17,7 +19,8 @@ namespace Avalonia
{
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
builder.UseWindowingSubsystem(() => AndroidPlatform.Initialize(builder.ApplicationType, options), "Android");
builder.UseSkia();
return builder;
}
@ -41,7 +44,7 @@ namespace Avalonia.Android
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
}
public static void Initialize(Type appType)
public static void Initialize(Type appType, AndroidPlatformOptions options)
{
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
@ -60,6 +63,11 @@ namespace Avalonia.Android
SkiaPlatform.Initialize();
((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)
.RegisterActivityLifecycleCallbacks(new ActivityTracker());
if (options.UseGpu)
{
EglPlatformOpenGlInterface.TryInitialize();
}
}
public IWindowImpl CreateWindow()
@ -72,4 +80,9 @@ namespace Avalonia.Android
throw new NotSupportedException();
}
}
public sealed class AndroidPlatformOptions
{
public bool UseGpu { get; set; } = true;
}
}

32
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -0,0 +1,32 @@
using System.Linq;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
{
private readonly EglPlatformOpenGlInterface _egl;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
{
_egl = egl;
_info = info;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle));
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl)
{
return new GlPlatformSurface(egl, info);
}
return null;
}
}
}

23
src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs

@ -0,0 +1,23 @@
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase
{
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglSurface _surface;
public GlRenderTarget(
EglPlatformOpenGlInterface egl,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
EglSurface surface)
: base(egl)
{
_info = info;
_surface = surface;
}
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
}
}

4
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -1,7 +1,11 @@
using System;
using System.Threading.Tasks;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;

17
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@ -0,0 +1,17 @@
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{
internal sealed class FramebufferManager : IFramebufferPlatformSurface
{
private readonly TopLevelImpl _topLevel;
public FramebufferManager(TopLevelImpl topLevel)
{
_topLevel = topLevel;
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface);
}
}

41
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -2,20 +2,29 @@ using System;
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
using Android.Runtime;
using Android.Views;
using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
@ -28,7 +37,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this };
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels);
@ -47,7 +57,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_keyboardHelper.HandleEvents = _handleEvents;
}
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY());
public IInputRoot InputRoot { get; private set; }
@ -62,7 +72,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
set
{
}
}
@ -82,9 +92,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public View View => _view;
internal InvalidationAwareSurfaceView InternalView => _view;
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces { get; }
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer };
public IRenderer CreateRenderer(IRenderRoot root)
{
@ -95,7 +107,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
_view.Visibility = ViewStates.Invisible;
}
public void Invalidate(Rect rect)
{
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
@ -196,7 +208,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPopupImpl CreatePopup() => null;
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle =>
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, _view.Holder.Surface.Handle);
public PixelSize Size => new PixelSize(_view.Holder.SurfaceFrame.Width(), _view.Holder.SurfaceFrame.Height());
public double Scaling => RenderScaling;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
throw new NotImplementedException();
}
}
}

11
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -5,14 +5,14 @@ using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.Specific.Helpers
{
public class AndroidKeyboardEventsHelper<TView> : IDisposable where TView :ITopLevelImpl, IAndroidView
internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl, IAndroidView
{
private TView _view;
private IInputElement _lastFocusedElement;
@ -46,9 +46,11 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
_view.Input(rawKeyEvent);
if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
@ -56,6 +58,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
_view.InputRoot,
Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input(rawTextEvent);

60
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -1,60 +0,0 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.Android.Resource", IsApplication=false)]
namespace Avalonia.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class String
{
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

5
src/Android/Avalonia.Android/SystemDialogImpl.cs

@ -2,18 +2,17 @@ using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android
{
internal class SystemDialogImpl : ISystemDialogImpl
{
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
throw new NotImplementedException();
}
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{
throw new NotImplementedException();
}

4
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -150,4 +150,4 @@
<Import Project="..\..\..\build\System.Memory.props" />
<Import Project="..\..\..\build\AndroidWorkarounds.props" />
<Import Project="..\..\..\build\LegacyProject.targets" />
</Project>
</Project>

4
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -4,7 +4,6 @@ using Android.Content.PM;
using Android.OS;
using Avalonia.Android;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
@ -38,8 +37,7 @@ namespace Avalonia.AndroidTestApplication
{
Styles.Add(new DefaultTheme());
var loader = new AvaloniaXamlLoader();
var baseLight = (IStyle)loader.Load(
var baseLight = (IStyle)AvaloniaXamlLoader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
Styles.Add(baseLight);

2
src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk />
<uses-sdk android:targetSdkVersion="29" />
<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

17
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -15,7 +14,7 @@ namespace Avalonia.AndroidTestApplication
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@ -26,8 +25,6 @@ namespace Avalonia.AndroidTestApplication
public static void UpdateIdValues()
{
global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello;
}
public partial class Attribute
@ -46,8 +43,8 @@ namespace Avalonia.AndroidTestApplication
public partial class Drawable
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
// aapt resource value: 0x7F010000
public const int Icon = 2130771968;
static Drawable()
{
@ -62,11 +59,11 @@ namespace Avalonia.AndroidTestApplication
public partial class String
{
// aapt resource value: 0x7f030001
public const int ApplicationName = 2130903041;
// aapt resource value: 0x7F020000
public const int ApplicationName = 2130837504;
// aapt resource value: 0x7f030000
public const int Hello = 2130903040;
// aapt resource value: 0x7F020001
public const int Hello = 2130837505;
static String()
{

3
src/Avalonia.Base/Avalonia.Base.csproj

@ -5,9 +5,6 @@
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"/>
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />

25
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@ -63,7 +63,7 @@ namespace Avalonia.Data.Converters
}
}
void OnPropertyChanged(object sender,PropertyChangedEventArgs args)
void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (string.IsNullOrWhiteSpace(args.PropertyName)
|| dependencyProperties?.Contains(args.PropertyName) == true)
@ -88,12 +88,7 @@ namespace Avalonia.Data.Converters
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
var call = Expression.Call
(
@ -114,11 +109,7 @@ namespace Avalonia.Data.Converters
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
Expression body;
@ -167,11 +158,7 @@ namespace Avalonia.Data.Converters
, System.Reflection.MethodInfo method)
{
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
var call = Expression.Call
(
instance,
@ -183,6 +170,8 @@ namespace Avalonia.Data.Converters
.Compile();
}
private static Expression? ConvertTarget(object? target, MethodInfo method) =>
target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType);
internal class WeakPropertyChangedProxy
{
@ -224,7 +213,7 @@ namespace Avalonia.Data.Converters
else
Unsubscribe();
}
}
}
}

8
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -14,7 +14,7 @@ namespace Avalonia.Data.Core.Plugins
{
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
/// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
@ -51,7 +51,7 @@ namespace Avalonia.Data.Core.Plugins
private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo = TryFindAndCacheProperty(type, propertyName);
@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
return propertyInfo;
}
private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
{
PropertyInfo found = null;
@ -90,7 +90,7 @@ namespace Avalonia.Data.Core.Plugins
private readonly PropertyInfo _property;
private bool _eventRaised;
public Accessor(WeakReference<object> reference, PropertyInfo property)
public Accessor(WeakReference<object> reference, PropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);

35
src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs

@ -3,14 +3,15 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Media;
using System;
using System.Diagnostics;
using Avalonia.Layout;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Used within the template of a <see cref="T:Avalonia.Controls.DataGrid" /> to specify the
/// Used within the template of a <see cref="T:Avalonia.Controls.DataGrid" /> to specify the
/// location in the control's visual tree where the rows are to be added.
/// </summary>
public sealed class DataGridRowsPresenter : Panel
@ -22,25 +23,10 @@ namespace Avalonia.Controls.Primitives
}
private double _measureHeightOffset = 0;
private double _effectiveViewPortHeight = 0;
public DataGridRowsPresenter()
{
EffectiveViewportChanged += OnEffectiveViewportChanged;
}
private void OnEffectiveViewportChanged(object sender, Layout.EffectiveViewportChangedEventArgs e)
{
if (_effectiveViewPortHeight != e.EffectiveViewport.Height)
{
_effectiveViewPortHeight = e.EffectiveViewport.Height;
InvalidateMeasure();
}
}
private double CalculateEstimatedAvailableHeight(Size availableSize)
{
if(!Double.IsPositiveInfinity(availableSize.Height))
if (!Double.IsPositiveInfinity(availableSize.Height))
{
return availableSize.Height + _measureHeightOffset;
}
@ -66,10 +52,10 @@ namespace Avalonia.Controls.Primitives
return base.ArrangeOverride(finalSize);
}
if(OwningGrid.RowsPresenterAvailableSize.HasValue)
if (OwningGrid.RowsPresenterAvailableSize.HasValue)
{
var availableHeight = OwningGrid.RowsPresenterAvailableSize.Value.Height;
if(!Double.IsPositiveInfinity(availableHeight))
if (!Double.IsPositiveInfinity(availableHeight))
{
_measureHeightOffset = finalSize.Height - availableHeight;
OwningGrid.RowsPresenterEstimatedAvailableHeight = finalSize.Height;
@ -126,7 +112,14 @@ namespace Avalonia.Controls.Primitives
{
if (double.IsInfinity(availableSize.Height))
{
availableSize = availableSize.WithHeight(_effectiveViewPortHeight);
if (VisualRoot is TopLevel topLevel)
{
double maxHeight = topLevel.IsArrangeValid ?
topLevel.Bounds.Height :
LayoutHelper.ApplyLayoutConstraints(topLevel, availableSize).Height;
availableSize = availableSize.WithHeight(maxHeight);
}
}
if (availableSize.Height == 0 || OwningGrid == null)

8
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -355,7 +355,13 @@ namespace Avalonia.Controls.Platform
}
else if (!item.IsPointerOverSubMenu)
{
item.IsSubMenuOpen = false;
DelayRun(() =>
{
if (!item.IsPointerOverSubMenu)
{
item.IsSubMenuOpen = false;
}
}, MenuShowDelay);
}
}
}

11
src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs

@ -0,0 +1,11 @@
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl
{
public ITextInputMethodImpl TextInputMethod { get; }
}
}

25
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -1,5 +1,6 @@
using System;
using System.Reactive.Linq;
using Avalonia.Input.TextInput;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
@ -378,19 +379,23 @@ namespace Avalonia.Controls.Presenters
if (_caretBlink)
{
var charPos = FormattedText.HitTestTextPosition(CaretIndex);
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
var (p1, p2) = GetCaretPoints();
context.DrawLine(
new Pen(caretBrush, 1),
new Point(x, y),
new Point(x, b));
p1, p2);
}
}
}
(Point, Point) GetCaretPoints()
{
var charPos = FormattedText.HitTestTextPosition(CaretIndex);
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
return (new Point(x, y), new Point(x, b));
}
public void ShowCaret()
{
_caretBlink = true;
@ -538,5 +543,11 @@ namespace Avalonia.Controls.Presenters
_caretBlink = !_caretBlink;
InvalidateVisual();
}
internal Rect GetCursorRectangle()
{
var (p1, p2) = GetCaretPoints();
return new Rect(p1, p2);
}
}
}

6
src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs

@ -2,6 +2,7 @@ using System;
using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
@ -17,6 +18,11 @@ namespace Avalonia.Controls.Primitives
{
public IInputElement? InputPassThroughElement { get; set; }
static LightDismissOverlayLayer()
{
BackgroundProperty.OverrideDefaultValue<LightDismissOverlayLayer>(Brushes.Transparent);
}
/// <summary>
/// Returns the light dismiss overlay for a specified visual.
/// </summary>

1
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -74,7 +74,6 @@ namespace Avalonia.Controls.Primitives
{
rv = new LightDismissOverlayLayer
{
Background = Brushes.Transparent,
IsVisible = false
};

7
src/Avalonia.Controls/TextBox.cs

@ -149,6 +149,7 @@ namespace Avalonia.Controls
private int _selectionStart;
private int _selectionEnd;
private TextPresenter _presenter;
private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
@ -161,6 +162,10 @@ namespace Avalonia.Controls
static TextBox()
{
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
TextInputMethodClientRequestedEvent.AddClassHandler<TextBox>((tb, e) =>
{
e.Client = tb._imClient;
});
}
public TextBox()
@ -437,7 +442,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter);
if (IsFocused)
{
_presenter?.ShowCaret();

41
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -0,0 +1,41 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
internal class TextBoxTextInputMethodClient : ITextInputMethodClient
{
private TextPresenter _presenter;
private IDisposable _subscription;
public Rect CursorRectangle => _presenter?.GetCursorRectangle() ?? default;
public event EventHandler CursorRectangleChanged;
public IVisual TextViewVisual => _presenter;
public event EventHandler TextViewVisualChanged;
public bool SupportsPreedit => false;
public void SetPreeditText(string text) => throw new NotSupportedException();
public bool SupportsSurroundingText => false;
public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException();
public event EventHandler SurroundingTextChanged;
public string TextBeforeCursor => null;
public string TextAfterCursor => null;
private void OnCaretIndexChanged(int index) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
public void SetPresenter(TextPresenter presenter)
{
_subscription?.Dispose();
_subscription = null;
_presenter = presenter;
if (_presenter != null)
{
_subscription = _presenter.GetObservable(TextPresenter.CaretIndexProperty)
.Subscribe(OnCaretIndexChanged);
}
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
}
}
}

6
src/Avalonia.Controls/TopLevel.cs

@ -1,8 +1,10 @@
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
@ -31,6 +33,7 @@ namespace Avalonia.Controls
ICloseable,
IStyleHost,
ILogicalRoot,
ITextInputMethodRoot,
IWeakSubscriber<ResourcesChangedEventArgs>
{
/// <summary>
@ -489,5 +492,8 @@ namespace Avalonia.Controls
if (focused == this)
KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
}
ITextInputMethodImpl ITextInputMethodRoot.InputMethod =>
(PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod;
}
}

8
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -79,8 +79,8 @@
<DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" />
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
<TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
<TextBlock Text="{Binding Version, StringFormat=Avalonia {0}}" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" IsVisible="{Binding IsDevelopmentBuild}" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
</StackPanel>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
@ -99,7 +99,7 @@
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2020 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
<TextBlock Text="© 2021 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel>
</Grid>
</Window>
</Window>

9
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Controls;
@ -7,12 +8,18 @@ namespace Avalonia.Dialogs
{
public class AboutAvaloniaDialog : Window
{
private static readonly Version s_version = typeof(AboutAvaloniaDialog).Assembly.GetName().Version;
public static string Version { get; } = s_version.ToString(2);
public static bool IsDevelopmentBuild { get; } = s_version.Revision == 999;
public AboutAvaloniaDialog()
{
AvaloniaXamlLoader.Load(this);
DataContext = this;
}
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))

110
src/Avalonia.FreeDesktop/DBusCallQueue.cs

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Avalonia.FreeDesktop
{
class DBusCallQueue
{
private readonly Func<Exception, Task> _errorHandler;
class Item
{
public Func<Task> Callback;
public Action<Exception> OnFinish;
}
private Queue<Item> _q = new Queue<Item>();
private bool _processing;
public DBusCallQueue(Func<Exception, Task> errorHandler)
{
_errorHandler = errorHandler;
}
public void Enqueue(Func<Task> cb)
{
_q.Enqueue(new Item
{
Callback = cb
});
Process();
}
public Task EnqueueAsync(Func<Task> cb)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
_q.Enqueue(new Item
{
Callback = cb,
OnFinish = e =>
{
if (e == null)
tcs.TrySetResult(0);
else
tcs.TrySetException(e);
}
});
Process();
return tcs.Task;
}
public Task<T> EnqueueAsync<T>(Func<Task<T>> cb)
{
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
_q.Enqueue(new Item
{
Callback = async () =>
{
var res = await cb();
tcs.TrySetResult(res);
},
OnFinish = e =>
{
if (e != null)
tcs.TrySetException(e);
}
});
Process();
return tcs.Task;
}
async void Process()
{
if(_processing)
return;
_processing = true;
try
{
while (_q.Count > 0)
{
var item = _q.Dequeue();
try
{
await item.Callback();
item.OnFinish?.Invoke(null);
}
catch(Exception e)
{
if (item.OnFinish != null)
item.OnFinish(e);
else
await _errorHandler(e);
}
}
}
finally
{
_processing = false;
}
}
public void FailAll()
{
while (_q.Count>0)
{
var item = _q.Dequeue();
item.OnFinish?.Invoke(new OperationCanceledException());
}
}
}
}

11
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -1,5 +1,6 @@
using System;
using System.Threading;
using Avalonia.Logging;
using Avalonia.Threading;
using Tmds.DBus;
@ -48,8 +49,10 @@ namespace Avalonia.FreeDesktop
}
public static Connection Connection { get; private set; }
public static Exception TryInitialize(string dbusAddress = null)
public static Connection TryInitialize(string dbusAddress = null)
{
if (Connection != null)
return Connection;
var oldContext = SynchronizationContext.Current;
try
{
@ -70,13 +73,15 @@ namespace Avalonia.FreeDesktop
}
catch (Exception e)
{
return e;
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(null, "Unable to connect to DBus: " + e);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
return null;
return Connection;
}
}
}

288
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Logging;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
{
internal class DBusInputMethodFactory<T> : IX11InputMethodFactory where T : ITextInputMethodImpl, IX11InputMethodControl
{
private readonly Func<IntPtr, T> _factory;
public DBusInputMethodFactory(Func<IntPtr, T> factory)
{
_factory = factory;
}
public (ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid)
{
var im = _factory(xid);
return (im, im);
}
}
internal abstract class DBusTextInputMethodBase : IX11InputMethodControl, ITextInputMethodImpl
{
private List<IDisposable> _disposables = new List<IDisposable>();
private Queue<string> _onlineNamesQueue = new Queue<string>();
protected Connection Connection { get; }
private readonly string[] _knownNames;
private bool _connecting;
private string _currentName;
private DBusCallQueue _queue;
private bool _controlActive, _windowActive;
private bool? _imeActive;
private Rect _logicalRect;
private PixelRect? _lastReportedRect;
private double _scaling = 1;
private PixelPoint _windowPosition;
protected bool IsConnected => _currentName != null;
public DBusTextInputMethodBase(Connection connection, params string[] knownNames)
{
_queue = new DBusCallQueue(QueueOnError);
Connection = connection;
_knownNames = knownNames;
Watch();
}
async void Watch()
{
foreach (var name in _knownNames)
_disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange));
}
protected abstract Task<bool> Connect(string name);
protected string GetAppName() =>
Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
private async void OnNameChange(ServiceOwnerChangedEventArgs args)
{
if (args.NewOwner != null && _currentName == null)
{
_onlineNamesQueue.Enqueue(args.ServiceName);
if(!_connecting)
{
_connecting = true;
try
{
while (_onlineNamesQueue.Count > 0)
{
var name = _onlineNamesQueue.Dequeue();
try
{
if (await Connect(name))
{
_onlineNamesQueue.Clear();
_currentName = name;
return;
}
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Unable to create IME input context:\n" + e);
}
}
}
finally
{
_connecting = false;
}
}
}
// IME has crashed
if (args.NewOwner == null && args.ServiceName == _currentName)
{
_currentName = null;
foreach(var s in _disposables)
s.Dispose();
_disposables.Clear();
OnDisconnected();
Reset();
// Watch again
Watch();
}
}
protected virtual Task Disconnect()
{
return Task.CompletedTask;
}
protected virtual void OnDisconnected()
{
}
protected virtual void Reset()
{
_lastReportedRect = null;
_imeActive = null;
}
async Task QueueOnError(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error:\n" + e);
try
{
await Disconnect();
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error while destroying the context:\n" + ex);
}
OnDisconnected();
_currentName = null;
}
protected void Enqueue(Func<Task> cb) => _queue.Enqueue(cb);
protected void AddDisposable(IDisposable d) => _disposables.Add(d);
public void Dispose()
{
foreach(var d in _disposables)
d.Dispose();
_disposables.Clear();
try
{
Disconnect().ContinueWith(_ => { });
}
catch
{
// fire and forget
}
_currentName = null;
}
protected abstract Task SetCursorRectCore(PixelRect rect);
protected abstract Task SetActiveCore(bool active);
protected abstract Task ResetContextCore();
protected abstract Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode);
void UpdateActive()
{
_queue.Enqueue(async () =>
{
if(!IsConnected)
return;
var active = _windowActive && _controlActive;
if (active != _imeActive)
{
_imeActive = active;
await SetActiveCore(active);
}
});
}
void IX11InputMethodControl.SetWindowActive(bool active)
{
_windowActive = active;
UpdateActive();
}
void ITextInputMethodImpl.SetActive(bool active)
{
_controlActive = active;
UpdateActive();
}
bool IX11InputMethodControl.IsEnabled => IsConnected && _imeActive == true;
async ValueTask<bool> IX11InputMethodControl.HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode)
{
try
{
return await _queue.EnqueueAsync(async () => await HandleKeyCore(args, keyVal, keyCode));
}
// Disconnected
catch (OperationCanceledException)
{
return false;
}
// Error, disconnect
catch (Exception e)
{
await QueueOnError(e);
return false;
}
}
private Action<string> _onCommit;
event Action<string> IX11InputMethodControl.Commit
{
add => _onCommit += value;
remove => _onCommit -= value;
}
protected void FireCommit(string s) => _onCommit?.Invoke(s);
private Action<X11InputMethodForwardedKey> _onForward;
event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey
{
add => _onForward += value;
remove => _onForward -= value;
}
protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k);
void UpdateCursorRect()
{
_queue.Enqueue(async () =>
{
if(!IsConnected)
return;
var cursorRect = PixelRect.FromRect(_logicalRect, _scaling);
cursorRect = cursorRect.Translate(_windowPosition);
if (cursorRect != _lastReportedRect)
{
_lastReportedRect = cursorRect;
await SetCursorRectCore(cursorRect);
}
});
}
void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling)
{
_windowPosition = position;
_scaling = scaling;
UpdateCursorRect();
}
void ITextInputMethodImpl.SetCursorRect(Rect rect)
{
_logicalRect = rect;
UpdateCursorRect();
}
public abstract void SetOptions(TextInputOptionsQueryEventArgs options);
void ITextInputMethodImpl.Reset()
{
Reset();
_queue.Enqueue(async () =>
{
if (!IsConnected)
return;
await ResetContextCore();
});
}
}
}

69
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
[DBusInterface("org.fcitx.Fcitx.InputMethod")]
interface IFcitxInputMethod : IDBusObject
{
Task<(int icid, bool enable, uint keyval1, uint state1, uint keyval2, uint state2)> CreateICv3Async(
string Appname, int Pid);
}
[DBusInterface("org.fcitx.Fcitx.InputContext")]
interface IFcitxInputContext : IDBusObject
{
Task EnableICAsync();
Task CloseICAsync();
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task MouseEventAsync(int X);
Task SetCursorLocationAsync(int X, int Y);
Task SetCursorRectAsync(int X, int Y, int W, int H);
Task SetCapacityAsync(uint Caps);
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
Task DestroyICAsync();
Task<int> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time);
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputContext1")]
interface IFcitxInputContext1 : IDBusObject
{
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task SetCursorRectAsync(int X, int Y, int W, int H);
Task SetCapabilityAsync(ulong Caps);
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
Task DestroyICAsync();
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputMethod1")]
interface IFcitxInputMethod1 : IDBusObject
{
Task<(ObjectPath path, byte[] data)> CreateInputContextAsync((string, string)[] arg0);
}
}

67
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs

@ -0,0 +1,67 @@
using System;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
enum FcitxKeyEventType
{
FCITX_PRESS_KEY,
FCITX_RELEASE_KEY
};
[Flags]
enum FcitxCapabilityFlags
{
CAPACITY_NONE = 0,
CAPACITY_CLIENT_SIDE_UI = (1 << 0),
CAPACITY_PREEDIT = (1 << 1),
CAPACITY_CLIENT_SIDE_CONTROL_STATE = (1 << 2),
CAPACITY_PASSWORD = (1 << 3),
CAPACITY_FORMATTED_PREEDIT = (1 << 4),
CAPACITY_CLIENT_UNFOCUS_COMMIT = (1 << 5),
CAPACITY_SURROUNDING_TEXT = (1 << 6),
CAPACITY_EMAIL = (1 << 7),
CAPACITY_DIGIT = (1 << 8),
CAPACITY_UPPERCASE = (1 << 9),
CAPACITY_LOWERCASE = (1 << 10),
CAPACITY_NOAUTOUPPERCASE = (1 << 11),
CAPACITY_URL = (1 << 12),
CAPACITY_DIALABLE = (1 << 13),
CAPACITY_NUMBER = (1 << 14),
CAPACITY_NO_ON_SCREEN_KEYBOARD = (1 << 15),
CAPACITY_SPELLCHECK = (1 << 16),
CAPACITY_NO_SPELLCHECK = (1 << 17),
CAPACITY_WORD_COMPLETION = (1 << 18),
CAPACITY_UPPERCASE_WORDS = (1 << 19),
CAPACITY_UPPERCASE_SENTENCES = (1 << 20),
CAPACITY_ALPHA = (1 << 21),
CAPACITY_NAME = (1 << 22),
CAPACITY_GET_IM_INFO_ON_FOCUS = (1 << 23),
CAPACITY_RELATIVE_CURSOR_RECT = (1 << 24),
};
[Flags]
enum FcitxKeyState
{
FcitxKeyState_None = 0,
FcitxKeyState_Shift = 1 << 0,
FcitxKeyState_CapsLock = 1 << 1,
FcitxKeyState_Ctrl = 1 << 2,
FcitxKeyState_Alt = 1 << 3,
FcitxKeyState_Alt_Shift = FcitxKeyState_Alt | FcitxKeyState_Shift,
FcitxKeyState_Ctrl_Shift = FcitxKeyState_Ctrl | FcitxKeyState_Shift,
FcitxKeyState_Ctrl_Alt = FcitxKeyState_Ctrl | FcitxKeyState_Alt,
FcitxKeyState_Ctrl_Alt_Shift =
FcitxKeyState_Ctrl | FcitxKeyState_Alt | FcitxKeyState_Shift,
FcitxKeyState_NumLock = 1 << 4,
FcitxKeyState_Super = 1 << 6,
FcitxKeyState_ScrollLock = 1 << 7,
FcitxKeyState_MousePressed = 1 << 8,
FcitxKeyState_HandledMask = 1 << 24,
FcitxKeyState_IgnoredMask = 1 << 25,
FcitxKeyState_Super2 = 1 << 26,
FcitxKeyState_Hyper = 1 << 27,
FcitxKeyState_Meta = 1 << 28,
FcitxKeyState_UsedMask = 0x5c001fff
};
}

51
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs

@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
internal class FcitxICWrapper
{
private readonly IFcitxInputContext1 _modern;
private readonly IFcitxInputContext _old;
public FcitxICWrapper(IFcitxInputContext old)
{
_old = old;
}
public FcitxICWrapper(IFcitxInputContext1 modern)
{
_modern = modern;
}
public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern.FocusInAsync();
public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern.FocusOutAsync();
public Task ResetAsync() => _old?.ResetAsync() ?? _modern.ResetAsync();
public Task SetCursorRectAsync(int x, int y, int w, int h) =>
_old?.SetCursorRectAsync(x, y, w, h) ?? _modern.SetCursorRectAsync(x, y, w, h);
public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern.DestroyICAsync();
public async Task<bool> ProcessKeyEventAsync(uint keyVal, uint keyCode, uint state, int type, uint time)
{
if(_old!=null)
return await _old.ProcessKeyEventAsync(keyVal, keyCode, state, type, time) != 0;
return await _modern.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time);
}
public Task<IDisposable> WatchCommitStringAsync(Action<string> handler) =>
_old?.WatchCommitStringAsync(handler) ?? _modern.WatchCommitStringAsync(handler);
public Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler)
{
return _old?.WatchForwardKeyAsync(handler)
?? _modern.WatchForwardKeyAsync(ev =>
handler((ev.keyval, ev.state, ev.type ? 1 : 0)));
}
public Task SetCapacityAsync(uint flags) =>
_old?.SetCapacityAsync(flags) ?? _modern.SetCapabilityAsync(flags);
}
}

149
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@ -0,0 +1,149 @@
using System;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
internal class FcitxX11TextInputMethod : DBusTextInputMethodBase
{
private FcitxICWrapper _context;
private FcitxCapabilityFlags? _lastReportedFlags;
public FcitxX11TextInputMethod(Connection connection) : base(connection,
"org.fcitx.Fcitx",
"org.freedesktop.portal.Fcitx"
)
{
}
protected override async Task<bool> Connect(string name)
{
if (name == "org.fcitx.Fcitx")
{
var method = Connection.CreateProxy<IFcitxInputMethod>(name, "/inputmethod");
var resp = await method.CreateICv3Async(GetAppName(),
Process.GetCurrentProcess().Id);
var proxy = Connection.CreateProxy<IFcitxInputContext>(name,
"/inputcontext_" + resp.icid);
_context = new FcitxICWrapper(proxy);
}
else
{
var method = Connection.CreateProxy<IFcitxInputMethod1>(name, "/inputmethod");
var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) });
var proxy = Connection.CreateProxy<IFcitxInputContext1>(name, resp.path);
_context = new FcitxICWrapper(proxy);
}
AddDisposable(await _context.WatchCommitStringAsync(OnCommitString));
AddDisposable(await _context.WatchForwardKeyAsync(OnForward));
return true;
}
protected override Task Disconnect() => _context.DestroyICAsync();
protected override void OnDisconnected() => _context = null;
protected override void Reset()
{
_lastReportedFlags = null;
base.Reset();
}
protected override Task SetCursorRectCore(PixelRect cursorRect) =>
_context.SetCursorRectAsync(cursorRect.X, cursorRect.Y, Math.Max(1, cursorRect.Width),
Math.Max(1, cursorRect.Height));
protected override Task SetActiveCore(bool active)
{
if (active)
return _context.FocusInAsync();
else
return _context.FocusOutAsync();
}
protected override Task ResetContextCore() => _context.ResetAsync();
protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{
FcitxKeyState state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
state |= FcitxKeyState.FcitxKeyState_Ctrl;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
state |= FcitxKeyState.FcitxKeyState_Alt;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
state |= FcitxKeyState.FcitxKeyState_Shift;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
state |= FcitxKeyState.FcitxKeyState_Super;
var type = args.Type == RawKeyEventType.KeyDown ?
FcitxKeyEventType.FCITX_PRESS_KEY :
FcitxKeyEventType.FCITX_RELEASE_KEY;
return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type,
(uint)args.Timestamp).ConfigureAwait(false);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options) =>
Enqueue(async () =>
{
if(_context == null)
return;
FcitxCapabilityFlags flags = default;
if (options.Lowercase)
flags |= FcitxCapabilityFlags.CAPACITY_LOWERCASE;
if (options.Uppercase)
flags |= FcitxCapabilityFlags.CAPACITY_UPPERCASE;
if (!options.AutoCapitalization)
flags |= FcitxCapabilityFlags.CAPACITY_NOAUTOUPPERCASE;
if (options.ContentType == TextInputContentType.Email)
flags |= FcitxCapabilityFlags.CAPACITY_EMAIL;
else if (options.ContentType == TextInputContentType.Number)
flags |= FcitxCapabilityFlags.CAPACITY_NUMBER;
else if (options.ContentType == TextInputContentType.Password)
flags |= FcitxCapabilityFlags.CAPACITY_PASSWORD;
else if (options.ContentType == TextInputContentType.Phone)
flags |= FcitxCapabilityFlags.CAPACITY_DIALABLE;
else if (options.ContentType == TextInputContentType.Url)
flags |= FcitxCapabilityFlags.CAPACITY_URL;
if (flags != _lastReportedFlags)
{
_lastReportedFlags = flags;
await _context.SetCapacityAsync((uint)flags);
}
});
private void OnForward((uint keyval, uint state, int type) ev)
{
var state = (FcitxKeyState)ev.state;
KeyModifiers mods = default;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Ctrl))
mods |= KeyModifiers.Control;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Alt))
mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Shift))
mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Super))
mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey
{
Modifiers = mods,
KeyVal = (int)ev.keyval,
Type = ev.type == (int)FcitxKeyEventType.FCITX_PRESS_KEY ?
RawKeyEventType.KeyDown :
RawKeyEventType.KeyUp
});
}
private void OnCommitString(string s) => FireCommit(s);
}
}

52
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
[DBusInterface("org.freedesktop.IBus.InputContext")]
interface IIBusInputContext : IDBusObject
{
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State);
Task SetCursorLocationAsync(int X, int Y, int W, int H);
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task SetCapabilitiesAsync(uint Caps);
Task PropertyActivateAsync(string Name, int State);
Task SetEngineAsync(string Name);
Task<object> GetEngineAsync();
Task DestroyAsync();
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos);
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception> onError = null);
}
[DBusInterface("org.freedesktop.IBus.Portal")]
interface IIBusPortal : IDBusObject
{
Task<ObjectPath> CreateInputContextAsync(string Name);
}
}

45
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs

@ -0,0 +1,45 @@
using System;
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
[Flags]
internal enum IBusModifierMask
{
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,
HandledMask = 1 << 24,
ForwardMask = 1 << 25,
IgnoredMask = ForwardMask,
SuperMask = 1 << 26,
HyperMask = 1 << 27,
MetaMask = 1 << 28,
ReleaseMask = 1 << 30,
ModifierMask = 0x5c001fff
}
[Flags]
internal enum IBusCapability
{
CapPreeditText = 1 << 0,
CapAuxiliaryText = 1 << 1,
CapLookupTable = 1 << 2,
CapFocus = 1 << 3,
CapProperty = 1 << 4,
CapSurroundingText = 1 << 5,
}
}

105
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
internal class IBusX11TextInputMethod : DBusTextInputMethodBase
{
private IIBusInputContext _context;
public IBusX11TextInputMethod(Connection connection) : base(connection,
"org.freedesktop.portal.IBus")
{
}
protected override async Task<bool> Connect(string name)
{
var path =
await Connection.CreateProxy<IIBusPortal>(name, "/org/freedesktop/IBus")
.CreateInputContextAsync(GetAppName());
_context = Connection.CreateProxy<IIBusInputContext>(name, path);
AddDisposable(await _context.WatchCommitTextAsync(OnCommitText));
AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey));
Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus));
return true;
}
private void OnForwardKey((uint keyval, uint keycode, uint state) k)
{
var state = (IBusModifierMask)k.state;
KeyModifiers mods = default;
if (state.HasFlagCustom(IBusModifierMask.ControlMask))
mods |= KeyModifiers.Control;
if (state.HasFlagCustom(IBusModifierMask.Mod1Mask))
mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(IBusModifierMask.ShiftMask))
mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(IBusModifierMask.Mod4Mask))
mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey
{
KeyVal = (int)k.keyval,
Type = state.HasFlagCustom(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
Modifiers = mods
});
}
private void OnCommitText(object wtf)
{
// Hello darkness, my old friend
var prop = wtf.GetType().GetField("Item3");
if (prop != null)
{
var text = (string)prop.GetValue(wtf);
if (!string.IsNullOrEmpty(text))
FireCommit(text);
}
}
protected override Task Disconnect() => _context.DestroyAsync();
protected override void OnDisconnected()
{
_context = null;
base.OnDisconnected();
}
protected override Task SetCursorRectCore(PixelRect rect)
=> _context.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height);
protected override Task SetActiveCore(bool active)
=> active ? _context.FocusInAsync() : _context.FocusOutAsync();
protected override Task ResetContextCore()
=> _context.ResetAsync();
protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{
IBusModifierMask state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
state |= IBusModifierMask.ControlMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
state |= IBusModifierMask.Mod1Mask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
state |= IBusModifierMask.ShiftMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
state |= IBusModifierMask.Mod4Mask;
if (args.Type == RawKeyEventType.KeyUp)
state |= IBusModifierMask.ReleaseMask;
return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options)
{
// No-op, because ibus
}
}
}

53
src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.FreeDesktop.DBusIme.IBus;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
{
public class X11DBusImeHelper
{
private static readonly Dictionary<string, Func<Connection, IX11InputMethodFactory>> KnownMethods =
new Dictionary<string, Func<Connection, IX11InputMethodFactory>>
{
["fcitx"] = conn =>
new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn)),
["ibus"] = conn =>
new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn))
};
static Func<Connection, IX11InputMethodFactory> DetectInputMethod()
{
foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" })
{
var value = Environment.GetEnvironmentVariable(name);
if (value == "none")
return null;
if (value != null && KnownMethods.TryGetValue(value, out var factory))
return factory;
}
return null;
}
public static bool DetectAndRegister()
{
var factory = DetectInputMethod();
if (factory != null)
{
var conn = DBusHelper.TryInitialize();
if (conn != null)
{
AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn));
return true;
}
}
return false;
}
}
}

31
src/Avalonia.FreeDesktop/IX11InputMethod.cs

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
namespace Avalonia.FreeDesktop
{
public interface IX11InputMethodFactory
{
(ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid);
}
public struct X11InputMethodForwardedKey
{
public int KeyVal { get; set; }
public KeyModifiers Modifiers { get; set; }
public RawKeyEventType Type { get; set; }
}
public interface IX11InputMethodControl : IDisposable
{
void SetWindowActive(bool active);
bool IsEnabled { get; }
ValueTask<bool> HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode);
event Action<string> Commit;
event Action<X11InputMethodForwardedKey> ForwardKey;
void UpdateWindowInfo(PixelPoint position, double scaling);
}
}

35
src/Avalonia.Input/InputElement.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -103,6 +104,22 @@ namespace Avalonia.Input
RoutedEvent.Register<InputElement, TextInputEventArgs>(
"TextInput",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextInputMethodClientRequested"/> event.
/// </summary>
public static readonly RoutedEvent<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequestedEvent =
RoutedEvent.Register<InputElement, TextInputMethodClientRequestedEventArgs>(
"TextInputMethodClientRequested",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextInputOptionsQuery"/> event.
/// </summary>
public static readonly RoutedEvent<TextInputOptionsQueryEventArgs> TextInputOptionsQueryEvent =
RoutedEvent.Register<InputElement, TextInputOptionsQueryEventArgs>(
"TextInputOptionsQuery",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerEnter"/> event.
@ -243,6 +260,24 @@ namespace Avalonia.Input
add { AddHandler(TextInputEvent, value); }
remove { RemoveHandler(TextInputEvent, value); }
}
/// <summary>
/// Occurs when an input element gains input focus and input method is looking for the corresponding client
/// </summary>
public event EventHandler<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequested
{
add { AddHandler(TextInputMethodClientRequestedEvent, value); }
remove { RemoveHandler(TextInputMethodClientRequestedEvent, value); }
}
/// <summary>
/// Occurs when an input element gains input focus and input method is asking for required content options
/// </summary>
public event EventHandler<TextInputOptionsQueryEventArgs> TextInputOptionsQuery
{
add { AddHandler(TextInputOptionsQueryEvent, value); }
remove { RemoveHandler(TextInputOptionsQueryEvent, value); }
}
/// <summary>
/// Occurs when the pointer enters the control.

6
src/Avalonia.Input/KeyboardDevice.cs

@ -1,6 +1,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -18,6 +19,10 @@ namespace Avalonia.Input
public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
// This should live in the FocusManager, but with the current outdated architecture
// the source of truth about the input focus is in KeyboardDevice
private readonly TextInputMethodManager _textInputManager = new TextInputMethodManager();
public IInputElement? FocusedElement
{
@ -40,6 +45,7 @@ namespace Avalonia.Input
}
RaisePropertyChanged();
_textInputManager.SetFocusedElement(value);
}
}

60
src/Avalonia.Input/TextInput/ITextInputMethodClient.cs

@ -0,0 +1,60 @@
using System;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
public interface ITextInputMethodClient
{
/// <summary>
/// The cursor rectangle relative to the TextViewVisual
/// </summary>
Rect CursorRectangle { get; }
/// <summary>
/// Should be fired when cursor rectangle is changed inside the TextViewVisual
/// </summary>
event EventHandler CursorRectangleChanged;
/// <summary>
/// The visual that's showing the text
/// </summary>
IVisual TextViewVisual { get; }
/// <summary>
/// Should be fired when text-hosting visual is changed
/// </summary>
event EventHandler TextViewVisualChanged;
/// <summary>
/// Indicates if TextViewVisual is capable of displaying non-commited input on the cursor position
/// </summary>
bool SupportsPreedit { get; }
/// <summary>
/// Sets the non-commited input string
/// </summary>
void SetPreeditText(string text);
/// <summary>
/// Indicates if text input client is capable of providing the text around the cursor
/// </summary>
bool SupportsSurroundingText { get; }
/// <summary>
/// Returns the text around the cursor, usually the current paragraph, the cursor position inside that text and selection start position
/// </summary>
TextInputMethodSurroundingText SurroundingText { get; }
/// <summary>
/// Should be fired when surrounding text changed
/// </summary>
event EventHandler SurroundingTextChanged;
/// <summary>
/// Returns the text before the cursor. Must return a non-empty string if cursor is not at the beginning of the text entry
/// </summary>
string TextBeforeCursor { get; }
/// <summary>
/// Returns the text before the cursor. Must return a non-empty string if cursor is not at the end of the text entry
/// </summary>
string TextAfterCursor { get; }
}
public struct TextInputMethodSurroundingText
{
public string Text { get; set; }
public int CursorOffset { get; set; }
public int AnchorOffset { get; set; }
}
}

15
src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs

@ -0,0 +1,15 @@
namespace Avalonia.Input.TextInput
{
public interface ITextInputMethodImpl
{
void SetActive(bool active);
void SetCursorRect(Rect rect);
void SetOptions(TextInputOptionsQueryEventArgs options);
void Reset();
}
public interface ITextInputMethodRoot : IInputRoot
{
ITextInputMethodImpl InputMethod { get; }
}
}

101
src/Avalonia.Input/TextInput/InputMethodManager.cs

@ -0,0 +1,101 @@
using System;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
internal class TextInputMethodManager
{
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
private ITextInputMethodClient? _client;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
public TextInputMethodManager() => _transformTracker.MatrixChanged += UpdateCursorRect;
private ITextInputMethodClient? Client
{
get => _client;
set
{
if(_client == value)
return;
if (_client != null)
{
_client.CursorRectangleChanged -= OnCursorRectangleChanged;
_client.TextViewVisualChanged -= OnTextViewVisualChanged;
}
_client = value;
if (_client != null)
{
_client.CursorRectangleChanged += OnCursorRectangleChanged;
_client.TextViewVisualChanged += OnTextViewVisualChanged;
var optionsQuery = new TextInputOptionsQueryEventArgs
{
RoutedEvent = InputElement.TextInputOptionsQueryEvent
};
_focusedElement?.RaiseEvent(optionsQuery);
_im?.Reset();
_im?.SetOptions(optionsQuery);
_transformTracker?.SetVisual(_client?.TextViewVisual);
UpdateCursorRect();
_im?.SetActive(true);
}
else
{
_im?.SetActive(false);
_transformTracker.SetVisual(null);
}
}
}
private void OnTextViewVisualChanged(object sender, EventArgs e)
=> _transformTracker.SetVisual(_client?.TextViewVisual);
private void UpdateCursorRect()
{
if (_im == null || _client == null || _focusedElement?.VisualRoot == null)
return;
var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot);
if (transform == null)
_im.SetCursorRect(default);
else
_im.SetCursorRect(_client.CursorRectangle.TransformToAABB(transform.Value));
}
private void OnCursorRectangleChanged(object sender, EventArgs e)
{
if (sender == _client)
UpdateCursorRect();
}
public void SetFocusedElement(IInputElement? element)
{
if(_focusedElement == element)
return;
_focusedElement = element;
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
if(_im != inputMethod)
_im?.SetActive(false);
_im = inputMethod;
if (_focusedElement == null || _im == null)
{
Client = null;
_im?.SetActive(false);
return;
}
var clientQuery = new TextInputMethodClientRequestedEventArgs
{
RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
};
_focusedElement.RaiseEvent(clientQuery);
Client = clientQuery.Client;
}
}
}

12
src/Avalonia.Input/TextInput/TextInputContentType.cs

@ -0,0 +1,12 @@
namespace Avalonia.Input.TextInput
{
public enum TextInputContentType
{
Normal = 0,
Email = 1,
Phone = 2,
Number = 3,
Url = 4,
Password = 5
}
}

12
src/Avalonia.Input/TextInput/TextInputMethodClientRequestedEventArgs.cs

@ -0,0 +1,12 @@
using Avalonia.Interactivity;
namespace Avalonia.Input.TextInput
{
public class TextInputMethodClientRequestedEventArgs : RoutedEventArgs
{
/// <summary>
/// Set this property to a valid text input client to enable input method interaction
/// </summary>
public ITextInputMethodClient? Client { get; set; }
}
}

32
src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs

@ -0,0 +1,32 @@
using Avalonia.Interactivity;
namespace Avalonia.Input.TextInput
{
public class TextInputOptionsQueryEventArgs : RoutedEventArgs
{
/// <summary>
/// The content type (mostly for determining the shape of the virtual keyboard)
/// </summary>
public TextInputContentType ContentType { get; set; }
/// <summary>
/// Text is multiline
/// </summary>
public bool Multiline { get; set; }
/// <summary>
/// Text is in lower case
/// </summary>
public bool Lowercase { get; set; }
/// <summary>
/// Text is in upper case
/// </summary>
public bool Uppercase { get; set; }
/// <summary>
/// Automatically capitalize letters at the start of the sentence
/// </summary>
public bool AutoCapitalization { get; set; }
/// <summary>
/// Text contains sensitive data like card numbers and should not be stored
/// </summary>
public bool IsSensitive { get; set; }
}
}

109
src/Avalonia.Input/TextInput/TransformTrackingHelper.cs

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
class TransformTrackingHelper : IDisposable
{
private IVisual? _visual;
private bool _queuedForUpdate;
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
public TransformTrackingHelper()
{
_propertyChangedHandler = PropertyChangedHandler;
}
public void SetVisual(IVisual? visual)
{
Dispose();
_visual = visual;
if (visual != null)
{
visual.AttachedToVisualTree += OnAttachedToVisualTree;
visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
if (visual.IsAttachedToVisualTree)
SubscribeToParents();
UpdateMatrix();
}
}
public Matrix? Matrix { get; private set; }
public event Action? MatrixChanged;
public void Dispose()
{
if(_visual == null)
return;
UnsubscribeFromParents();
_visual.AttachedToVisualTree -= OnAttachedToVisualTree;
_visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
_visual = null;
}
private void SubscribeToParents()
{
var visual = _visual;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
// false positive
while (visual != null)
{
if (visual is Visual v)
{
v.PropertyChanged += _propertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.VisualParent;
}
}
private void UnsubscribeFromParents()
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
void UpdateMatrix()
{
Matrix? matrix = null;
if (_visual != null && _visual.VisualRoot != null)
matrix = _visual.TransformToVisual(_visual.VisualRoot);
if (Matrix != matrix)
{
Matrix = matrix;
MatrixChanged?.Invoke();
}
}
private void OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs)
{
SubscribeToParents();
UpdateMatrix();
}
private void EnqueueForUpdate()
{
if(_queuedForUpdate)
return;
_queuedForUpdate = true;
Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.Render);
}
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == Visual.BoundsProperty)
EnqueueForUpdate();
}
private void OnDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs)
{
UnsubscribeFromParents();
UpdateMatrix();
}
}
}

4
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -30,8 +30,10 @@ namespace Avalonia.OpenGL.Egl
static Func<string, IntPtr> Load()
{
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
if(os == OperatingSystemType.Linux)
return Load("libEGL.so.1");
if (os == OperatingSystemType.Android)
return Load("libEGL.so");
throw new PlatformNotSupportedException();
}

2
src/Avalonia.OpenGL/GlInterface.cs

@ -128,7 +128,7 @@ namespace Avalonia.OpenGL
int dstY1,
int mask,
int filter);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)]
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint]
public GlBlitFramebuffer BlitFramebuffer { get; }
public delegate void GlGenRenderbuffers(int count, int[] res);

2
src/Avalonia.Themes.Default/CaptionButtons.xaml

@ -57,7 +57,7 @@
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
<Style Selector="CaptionButtons Panel#PART_FullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">

2
src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml

@ -56,7 +56,7 @@
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
<Style Selector="CaptionButtons Panel#PART_FullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">

4
src/Avalonia.X11/X11Clipboard.cs

@ -52,7 +52,7 @@ namespace Avalonia.X11
: null;
}
private unsafe void OnEvent(XEvent ev)
private unsafe void OnEvent(ref XEvent ev)
{
if (ev.type == XEventName.SelectionRequest)
{
@ -62,7 +62,7 @@ namespace Avalonia.X11
SelectionEvent =
{
type = XEventName.SelectionNotify,
send_event = true,
send_event = 1,
display = _x11.Display,
selection = sel.selection,
target = sel.target,

4
src/Avalonia.X11/X11Globals.cs

@ -123,7 +123,7 @@ namespace Avalonia.X11
}
}
private void HandleCompositionAtomOwnerEvents(XEvent ev)
private void HandleCompositionAtomOwnerEvents(ref XEvent ev)
{
if(ev.type == XEventName.DestroyNotify)
UpdateCompositingAtomOwner();
@ -154,7 +154,7 @@ namespace Avalonia.X11
}
}
private void OnRootWindowEvent(XEvent ev)
private void OnRootWindowEvent(ref XEvent ev)
{
if (ev.type == XEventName.PropertyNotify)
{

25
src/Avalonia.X11/X11Info.cs

@ -32,8 +32,10 @@ namespace Avalonia.X11
public IntPtr LastActivityTimestamp { get; set; }
public XVisualInfo? TransparentVisualInfo { get; set; }
public bool HasXim { get; set; }
public IntPtr DefaultFontSet { get; set; }
public unsafe X11Info(IntPtr display, IntPtr deferredDisplay)
public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim)
{
Display = display;
DeferredDisplay = deferredDisplay;
@ -43,9 +45,24 @@ namespace Avalonia.X11
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);
DefaultFontSet = XCreateFontSet(Display, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*",
out var _, out var _, IntPtr.Zero);
if (useXim)
{
XSetLocaleModifiers("");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (Xim != IntPtr.Zero)
HasXim = true;
}
if (Xim == IntPtr.Zero)
{
XSetLocaleModifiers("@im=none");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
XMatchVisualInfo(Display, DefaultScreen, 32, 4, out var visual);
if (visual.depth == 32)
TransparentVisualInfo = visual;

72
src/Avalonia.X11/X11Platform.cs

@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.FreeDesktop.DBusIme;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
@ -21,7 +23,8 @@ namespace Avalonia.X11
{
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice());
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
public Dictionary<IntPtr, X11PlatformThreading.EventHandler> Windows =
new Dictionary<IntPtr, X11PlatformThreading.EventHandler>();
public XI2Manager XI2;
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
@ -29,9 +32,24 @@ namespace Avalonia.X11
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
public X11Globals Globals { get; private set; }
[DllImport("libc")]
static extern void setlocale(int type, string s);
public void Initialize(X11PlatformOptions options)
{
Options = options;
bool useXim = false;
if (EnableIme(options))
{
// Attempt to configure DBus-based input method and check if we can fall back to XIM
if (!X11DBusImeHelper.DetectAndRegister() && ShouldUseXim())
useXim = true;
}
// XIM doesn't work at all otherwise
if (useXim)
setlocale(0, "");
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
@ -40,7 +58,8 @@ namespace Avalonia.X11
if (Display == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
XError.Init();
Info = new X11Info(Display, DeferredDisplay);
Info = new X11Info(Display, DeferredDisplay, useXim);
Globals = new X11Globals(this);
//TODO: log
if (options.UseDBusMenu)
@ -90,6 +109,54 @@ namespace Avalonia.X11
{
throw new NotSupportedException();
}
bool EnableIme(X11PlatformOptions options)
{
// Disable if explicitly asked by user
var avaloniaImModule = Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE");
if (avaloniaImModule == "none")
return false;
// Use value from options when specified
if (options.EnableIme.HasValue)
return options.EnableIme.Value;
// Automatically enable for CJK locales
var lang = Environment.GetEnvironmentVariable("LANG");
var isCjkLocale = lang != null &&
(lang.Contains("zh")
|| lang.Contains("ja")
|| lang.Contains("vi")
|| lang.Contains("ko"));
return isCjkLocale;
}
bool ShouldUseXim()
{
// Check if we are forbidden from using IME
if (Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "none"
|| Environment.GetEnvironmentVariable("GTK_IM_MODULE") == "none"
|| Environment.GetEnvironmentVariable("QT_IM_MODULE") == "none")
return true;
// Check if XIM is configured
var modifiers = Environment.GetEnvironmentVariable("XMODIFIERS");
if (modifiers == null)
return false;
if (modifiers.Contains("@im=none") || modifiers.Contains("@im=null"))
return false;
if (!modifiers.Contains("@im="))
return false;
// Check if we are configured to use it
if (Environment.GetEnvironmentVariable("GTK_IM_MODULE") == "xim"
|| Environment.GetEnvironmentVariable("QT_IM_MODULE") == "xim"
|| Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "xim")
return true;
return false;
}
}
}
@ -103,6 +170,7 @@ namespace Avalonia
public bool OverlayPopups { get; set; }
public bool UseDBusMenu { get; set; }
public bool UseDeferredRendering { get; set; } = true;
public bool? EnableIme { get; set; }
public IList<GlVersion> GlProfiles { get; set; } = new List<GlVersion>
{

11
src/Avalonia.X11/X11PlatformThreading.cs

@ -13,7 +13,9 @@ namespace Avalonia.X11
{
private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _display;
private readonly Dictionary<IntPtr, Action<XEvent>> _eventHandlers;
public delegate void EventHandler(ref XEvent xev);
private readonly Dictionary<IntPtr, EventHandler> _eventHandlers;
private Thread _mainThread;
[StructLayout(LayoutKind.Explicit)]
@ -162,13 +164,16 @@ namespace Avalonia.X11
Signaled?.Invoke(prio);
}
void HandleX11(CancellationToken cancellationToken)
unsafe void HandleX11(CancellationToken cancellationToken)
{
while (XPending(_display) != 0)
{
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if(XFilterEvent(ref xev, IntPtr.Zero))
continue;
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
try
@ -182,7 +187,7 @@ namespace Avalonia.X11
}
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
handler(ref xev);
}
finally
{

2
src/Avalonia.X11/X11Screens.cs

@ -76,7 +76,7 @@ namespace Avalonia.X11
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
}
private void OnEvent(XEvent ev)
private void OnEvent(ref XEvent ev)
{
// Invalidate cache on RRScreenChangeNotify
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify)

130
src/Avalonia.X11/X11Structs.cs

@ -53,7 +53,7 @@ namespace Avalonia.X11 {
internal struct XAnyEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
}
@ -62,7 +62,7 @@ namespace Avalonia.X11 {
internal struct XKeyEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr root;
@ -74,14 +74,14 @@ namespace Avalonia.X11 {
internal int y_root;
internal XModifierMask state;
internal int keycode;
internal bool same_screen;
internal int same_screen;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XButtonEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr root;
@ -93,14 +93,14 @@ namespace Avalonia.X11 {
internal int y_root;
internal XModifierMask state;
internal int button;
internal bool same_screen;
internal int same_screen;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XMotionEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr root;
@ -112,14 +112,14 @@ namespace Avalonia.X11 {
internal int y_root;
internal XModifierMask state;
internal byte is_hint;
internal bool same_screen;
internal int same_screen;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XCrossingEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr root;
@ -131,8 +131,8 @@ namespace Avalonia.X11 {
internal int y_root;
internal NotifyMode mode;
internal NotifyDetail detail;
internal bool same_screen;
internal bool focus;
internal int same_screen;
internal int focus;
internal XModifierMask state;
}
@ -140,7 +140,7 @@ namespace Avalonia.X11 {
internal struct XFocusChangeEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal int mode;
@ -151,7 +151,7 @@ namespace Avalonia.X11 {
internal struct XKeymapEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal byte key_vector0;
@ -192,7 +192,7 @@ namespace Avalonia.X11 {
internal struct XExposeEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal int x;
@ -206,7 +206,7 @@ namespace Avalonia.X11 {
internal struct XGraphicsExposeEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr drawable;
internal int x;
@ -222,7 +222,7 @@ namespace Avalonia.X11 {
internal struct XNoExposeEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr drawable;
internal int major_code;
@ -233,7 +233,7 @@ namespace Avalonia.X11 {
internal struct XVisibilityEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal int state;
@ -243,7 +243,7 @@ namespace Avalonia.X11 {
internal struct XCreateWindowEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr parent;
internal IntPtr window;
@ -252,14 +252,14 @@ namespace Avalonia.X11 {
internal int width;
internal int height;
internal int border_width;
internal bool override_redirect;
internal int override_redirect;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XDestroyWindowEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
@ -269,29 +269,29 @@ namespace Avalonia.X11 {
internal struct XUnmapEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
internal bool from_configure;
internal int from_configure;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XMapEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
internal bool override_redirect;
internal int override_redirect;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XMapRequestEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr parent;
internal IntPtr window;
@ -301,21 +301,21 @@ namespace Avalonia.X11 {
internal struct XReparentEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
internal IntPtr parent;
internal int x;
internal int y;
internal bool override_redirect;
internal int override_redirect;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XConfigureEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
@ -325,14 +325,14 @@ namespace Avalonia.X11 {
internal int height;
internal int border_width;
internal IntPtr above;
internal bool override_redirect;
internal int override_redirect;
}
[StructLayout(LayoutKind.Sequential)]
internal struct XGravityEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
@ -344,7 +344,7 @@ namespace Avalonia.X11 {
internal struct XResizeRequestEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal int width;
@ -355,7 +355,7 @@ namespace Avalonia.X11 {
internal struct XConfigureRequestEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr parent;
internal IntPtr window;
@ -373,7 +373,7 @@ namespace Avalonia.X11 {
internal struct XCirculateEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr xevent;
internal IntPtr window;
@ -384,7 +384,7 @@ namespace Avalonia.X11 {
internal struct XCirculateRequestEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr parent;
internal IntPtr window;
@ -395,7 +395,7 @@ namespace Avalonia.X11 {
internal struct XPropertyEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr atom;
@ -407,7 +407,7 @@ namespace Avalonia.X11 {
internal struct XSelectionClearEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr selection;
@ -418,7 +418,7 @@ namespace Avalonia.X11 {
internal struct XSelectionRequestEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr owner;
internal IntPtr requestor;
@ -432,7 +432,7 @@ namespace Avalonia.X11 {
internal struct XSelectionEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr requestor;
internal IntPtr selection;
@ -445,11 +445,11 @@ namespace Avalonia.X11 {
internal struct XColormapEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr colormap;
internal bool c_new;
internal int c_new;
internal int state;
}
@ -457,7 +457,7 @@ namespace Avalonia.X11 {
internal struct XClientMessageEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal IntPtr message_type;
@ -473,7 +473,7 @@ namespace Avalonia.X11 {
internal struct XMappingEvent {
internal XEventName type;
internal IntPtr serial;
internal bool send_event;
internal int send_event;
internal IntPtr display;
internal IntPtr window;
internal int request;
@ -518,6 +518,15 @@ namespace Avalonia.X11 {
internal IntPtr pad21;
internal IntPtr pad22;
internal IntPtr pad23;
internal IntPtr pad24;
internal IntPtr pad25;
internal IntPtr pad26;
internal IntPtr pad27;
internal IntPtr pad28;
internal IntPtr pad29;
internal IntPtr pad30;
internal IntPtr pad31;
internal IntPtr pad32;
}
[StructLayout(LayoutKind.Sequential)]
@ -525,7 +534,7 @@ namespace Avalonia.X11 {
{
internal int type; /* of event. Always GenericEvent */
internal IntPtr serial; /* # of last request processed */
internal bool send_event; /* true if from SendEvent request */
internal int send_event; /* true if from SendEvent request */
internal IntPtr display; /* Display the event was read from */
internal int extension; /* major opcode of extension that caused the event */
internal int evtype; /* actual event type. */
@ -672,10 +681,10 @@ namespace Avalonia.X11 {
internal int backing_store;
internal IntPtr backing_planes;
internal IntPtr backing_pixel;
internal bool save_under;
internal int save_under;
internal IntPtr event_mask;
internal IntPtr do_not_propagate_mask;
internal bool override_redirect;
internal int override_redirect;
internal IntPtr colormap;
internal IntPtr cursor;
}
@ -696,14 +705,14 @@ namespace Avalonia.X11 {
internal int backing_store;
internal IntPtr backing_planes;
internal IntPtr backing_pixel;
internal bool save_under;
internal int save_under;
internal IntPtr colormap;
internal bool map_installed;
internal int map_installed;
internal MapState map_state;
internal IntPtr all_event_masks;
internal IntPtr your_event_mask;
internal IntPtr do_not_propagate_mask;
internal bool override_direct;
internal int override_direct;
internal IntPtr screen;
public override string ToString ()
@ -1029,7 +1038,7 @@ namespace Avalonia.X11 {
internal int max_maps;
internal int min_maps;
internal int backing_store;
internal bool save_unders;
internal int save_unders;
internal IntPtr root_input_mask;
}
@ -1280,7 +1289,7 @@ namespace Avalonia.X11 {
internal int ts_y_origin;
internal IntPtr font;
internal GCSubwindowMode subwindow_mode;
internal bool graphics_exposures;
internal int graphics_exposures;
internal int clip_x_origin;
internal int clib_y_origin;
internal IntPtr clip_mask;
@ -1499,7 +1508,7 @@ namespace Avalonia.X11 {
[StructLayout(LayoutKind.Sequential)]
internal struct XWMHints {
internal IntPtr flags;
internal bool input;
internal int input;
internal XInitialState initial_state;
internal IntPtr icon_pixmap;
internal IntPtr icon_window;
@ -1708,19 +1717,30 @@ namespace Avalonia.X11 {
}
[StructLayout (LayoutKind.Sequential)]
internal struct XIMStyles
internal unsafe struct XIMStyles
{
public ushort count_styles;
public IntPtr supported_styles;
public IntPtr* supported_styles;
}
[StructLayout (LayoutKind.Sequential)]
[Serializable]
internal class XPoint
internal struct XPoint
{
public short X;
public short Y;
}
[StructLayout (LayoutKind.Sequential)]
[Serializable]
internal struct XRectangle
{
public short X;
public short Y;
public short W;
public short H;
}
[StructLayout (LayoutKind.Sequential)]
[Serializable]
@ -1798,7 +1818,7 @@ namespace Avalonia.X11 {
{
public ushort Length;
public IntPtr Feedback; // to XIMFeedbackStruct
public bool EncodingIsWChar;
public int EncodingIsWChar;
public IntPtr String; // it could be either char* or wchar_t*
}
@ -1850,6 +1870,8 @@ namespace Avalonia.X11 {
public const string XNClientWindow = "clientWindow";
public const string XNInputStyle = "inputStyle";
public const string XNFocusWindow = "focusWindow";
public const string XNResourceName = "resourceName";
public const string XNResourceClass = "resourceClass";
// XIMPreeditCallbacks delegate names.
public const string XNPreeditStartCallback = "preeditStartCallback";

208
src/Avalonia.X11/X11Window.Ime.cs

@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.FreeDesktop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
partial class X11Window
{
private ITextInputMethodImpl _ime;
private IX11InputMethodControl _imeControl;
private bool _processingIme;
private Queue<(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)> _imeQueue =
new Queue<(RawKeyEventArgs args, XEvent xev, int keyVal, int keyCode)>();
unsafe void CreateIC()
{
if (_x11.HasXim)
{
XGetIMValues(_x11.Xim, XNames.XNQueryInputStyle, out var supported_styles, IntPtr.Zero);
for (var c = 0; c < supported_styles->count_styles; c++)
{
var style = (XIMProperties)supported_styles->supported_styles[c];
if ((int)(style & XIMProperties.XIMPreeditPosition) != 0
&& ((int)(style & XIMProperties.XIMStatusNothing) != 0))
{
XPoint spot = default;
XRectangle area = default;
//using var areaS = new Utf8Buffer("area");
using var spotS = new Utf8Buffer("spotLocation");
using var fontS = new Utf8Buffer("fontSet");
var list = XVaCreateNestedList(0,
//areaS, &area,
spotS, &spot,
fontS, _x11.DefaultFontSet,
IntPtr.Zero);
_xic = XCreateIC(_x11.Xim,
XNames.XNClientWindow, _handle,
XNames.XNFocusWindow, _handle,
XNames.XNInputStyle, new IntPtr((int)style),
XNames.XNResourceName, _platform.Options.WmClass,
XNames.XNResourceClass, _platform.Options.WmClass,
XNames.XNPreeditAttributes, list,
IntPtr.Zero);
XFree(list);
break;
}
}
XFree(new IntPtr(supported_styles));
}
if (_xic == IntPtr.Zero)
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle,
new IntPtr((int)(XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing)),
XNames.XNClientWindow, _handle, XNames.XNFocusWindow, _handle, IntPtr.Zero);
}
void InitializeIme()
{
var ime = AvaloniaLocator.Current.GetService<IX11InputMethodFactory>()?.CreateClient(_handle);
if (ime == null && _x11.HasXim)
{
var xim = new XimInputMethod(this);
ime = (xim, xim);
}
if (ime != null)
{
(_ime, _imeControl) = ime.Value;
_imeControl.Commit += s =>
ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, s));
_imeControl.ForwardKey += ev =>
{
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, ev.Type, X11KeyTransform.ConvertKey((X11Key)ev.KeyVal),
(RawInputModifiers)ev.Modifiers));
};
}
}
void UpdateImePosition() => _imeControl?.UpdateWindowInfo(Position, RenderScaling);
void HandleKeyEvent(ref XEvent ev)
{
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
var filtered = ScheduleKeyInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot,
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev, (int)key, ev.KeyEvent.keycode);
if (ev.type == XEventName.KeyPress && !filtered)
TriggerClassicTextInputEvent(ref ev);
}
void TriggerClassicTextInputEvent(ref XEvent ev)
{
var text = TranslateEventToString(ref ev);
if (text != null)
ScheduleInput(
new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, text),
ref ev);
}
private const int ImeBufferSize = 64 * 1024;
[ThreadStatic] private static IntPtr ImeBuffer;
unsafe string TranslateEventToString(ref XEvent ev)
{
if (ImeBuffer == IntPtr.Zero)
ImeBuffer = Marshal.AllocHGlobal(ImeBufferSize);
var len = Xutf8LookupString(_xic, ref ev, ImeBuffer.ToPointer(), ImeBufferSize,
out _, out var istatus);
var status = (XLookupStatus)istatus;
if (len == 0)
return null;
string text;
if (status == XLookupStatus.XBufferOverflow)
return null;
else
text = Encoding.UTF8.GetString((byte*)ImeBuffer.ToPointer(), len);
if (text == null)
return null;
if (text.Length == 1)
{
if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
return null;
}
return text;
}
bool ScheduleKeyInput(RawKeyEventArgs args, ref XEvent xev, int keyval, int keycode)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
if (_imeControl != null && _imeControl.IsEnabled)
{
if (FilterIme(args, xev, keyval, keycode))
return true;
}
ScheduleInput(args);
return false;
}
bool FilterIme(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)
{
if (_ime == null)
return false;
_imeQueue.Enqueue((args, xev, keyval, keycode));
if (!_processingIme)
ProcessNextImeEvent();
return true;
}
async void ProcessNextImeEvent()
{
if(_processingIme)
return;
_processingIme = true;
try
{
while (_imeQueue.Count != 0)
{
var ev = _imeQueue.Dequeue();
if (_imeControl == null || !await _imeControl.HandleEventAsync(ev.args, ev.keyval, ev.keycode))
{
ScheduleInput(ev.args);
if (ev.args.Type == RawKeyEventType.KeyDown)
TriggerClassicTextInputEvent(ref ev.xev);
}
}
}
finally
{
_processingIme = false;
}
}
}
}

121
src/Avalonia.X11/X11Window.Xim.cs

@ -0,0 +1,121 @@
using System;
using System.Threading.Tasks;
using Avalonia.FreeDesktop;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
partial class X11Window
{
class XimInputMethod : ITextInputMethodImpl, IX11InputMethodControl
{
private readonly X11Window _parent;
private bool _controlActive, _windowActive, _imeActive;
private Rect? _queuedCursorRect;
public XimInputMethod(X11Window parent)
{
_parent = parent;
}
public void SetCursorRect(Rect rect)
{
var needEnqueue = _queuedCursorRect == null;
_queuedCursorRect = rect;
if(needEnqueue)
Dispatcher.UIThread.Post(() =>
{
if(_queuedCursorRect == null)
return;
var rc = _queuedCursorRect.Value;
_queuedCursorRect = null;
if (_parent._xic == IntPtr.Zero)
return;
rect *= _parent._scaling;
var pt = new XPoint
{
X = (short)Math.Min(Math.Max(rect.X, short.MinValue), short.MaxValue),
Y = (short)Math.Min(Math.Max(rect.Y + rect.Height, short.MinValue), short.MaxValue)
};
using var spotLoc = new Utf8Buffer(XNames.XNSpotLocation);
var list = XVaCreateNestedList(0, spotLoc, ref pt, IntPtr.Zero);
XSetICValues(_parent._xic, XNames.XNPreeditAttributes, list, IntPtr.Zero);
XFree(list);
}, DispatcherPriority.Background);
}
public void SetWindowActive(bool active)
{
_windowActive = active;
UpdateActive();
}
public void SetActive(bool active)
{
_controlActive = active;
UpdateActive();
}
private void UpdateActive()
{
var active = _windowActive && _controlActive;
if(_parent._xic == IntPtr.Zero)
return;
if (active != _imeActive)
{
_imeActive = active;
if (active)
{
Reset();
XSetICFocus(_parent._xic);
}
else
XUnsetICFocus(_parent._xic);
}
}
public void UpdateWindowInfo(PixelPoint position, double scaling)
{
// No-op
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
{
// No-op
}
public void Reset()
{
if(_parent._xic == IntPtr.Zero)
return;
var data = XmbResetIC(_parent._xic);
if (data != IntPtr.Zero)
XFree(data);
}
public void Dispose()
{
// No-op
}
public bool IsEnabled => false;
public ValueTask<bool> HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode) =>
new ValueTask<bool>(false);
public event Action<string> Commit;
public event Action<X11InputMethodForwardedKey> ForwardKey;
}
}
}

80
src/Avalonia.X11/X11Window.cs

@ -5,12 +5,14 @@ using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.FreeDesktop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
@ -22,9 +24,10 @@ using static Avalonia.X11.XLib;
// ReSharper disable StringLiteralTypo
namespace Avalonia.X11
{
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
ITopLevelImplWithNativeMenuExporter,
ITopLevelImplWithNativeControlHost
ITopLevelImplWithNativeControlHost,
ITopLevelImplWithTextInputMethod
{
private readonly AvaloniaX11Platform _platform;
private readonly IWindowImpl _popupParent;
@ -79,7 +82,7 @@ namespace Avalonia.X11
if (_popup)
{
attr.override_redirect = true;
attr.override_redirect = 1;
valueMask |= SetWindowValuemask.OverrideRedirect;
}
@ -178,11 +181,12 @@ namespace Avalonia.X11
Surfaces = surfaces.ToArray();
UpdateMotifHints();
UpdateSizeHints(null);
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None);
CreateIC();
XFlush(_x11.Display);
if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
@ -194,6 +198,7 @@ namespace Avalonia.X11
Paint?.Invoke(default);
return _handle != IntPtr.Zero;
}, TimeSpan.FromMilliseconds(100));
InitializeIme();
}
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
@ -350,15 +355,13 @@ namespace Avalonia.X11
(IRenderer)new X11ImmediateRendererProxy(root, loop);
}
void OnEvent(XEvent ev)
void OnEvent(ref XEvent ev)
{
lock (SyncRoot)
OnEventSync(ev);
OnEventSync(ref ev);
}
void OnEventSync(XEvent ev)
void OnEventSync(ref XEvent ev)
{
if(XFilterEvent(ref ev, _handle))
return;
if (ev.type == XEventName.MapNotify)
{
_mapped = true;
@ -386,9 +389,13 @@ namespace Avalonia.X11
if (ActivateTransientChildIfNeeded())
return;
Activated?.Invoke();
_imeControl?.SetWindowActive(true);
}
else if (ev.type == XEventName.FocusOut)
{
_imeControl?.SetWindowActive(false);
Deactivated?.Invoke();
}
else if (ev.type == XEventName.MotionNotify)
MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
@ -447,7 +454,7 @@ namespace Avalonia.X11
return;
var needEnqueue = (_configure == null);
_configure = ev.ConfigureEvent;
if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event)
if (ev.ConfigureEvent.override_redirect != 0 || ev.ConfigureEvent.send_event != 0)
_configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
else
{
@ -477,6 +484,7 @@ namespace Avalonia.X11
PositionChanged?.Invoke(npos);
updatedSizeViaScaling = UpdateScaling();
}
UpdateImePosition();
if (changedSize && !updatedSizeViaScaling && !_popup)
Resized?.Invoke(ClientSize);
@ -487,7 +495,8 @@ namespace Avalonia.X11
XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width,
ev.ConfigureEvent.height);
}
else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle)
else if (ev.type == XEventName.DestroyNotify
&& ev.DestroyWindowEvent.window == _handle)
{
Cleanup();
}
@ -507,39 +516,7 @@ namespace Avalonia.X11
{
if (ActivateTransientChildIfNeeded())
return;
var buffer = stackalloc byte[40];
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot,
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
X11KeyTransform.ConvertKey(key), 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(), _inputRoot, text),
ref ev);
}
}
HandleKeyEvent(ref ev);
}
}
@ -562,6 +539,7 @@ namespace Avalonia.X11
var oldScaledSize = ClientSize;
RenderScaling = newScaling;
ScalingChanged?.Invoke(RenderScaling);
UpdateImePosition();
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
if(!skipResize)
Resize(oldScaledSize, true);
@ -699,6 +677,7 @@ namespace Avalonia.X11
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
ScheduleInput(args);
}
public void ScheduleXI2Input(RawInputEventArgs args)
{
@ -781,6 +760,13 @@ namespace Avalonia.X11
void Cleanup()
{
if (_imeControl != null)
{
_imeControl.Dispose();
_imeControl = null;
_ime = null;
}
if (_xic != IntPtr.Zero)
{
XDestroyIC(_xic);
@ -957,7 +943,7 @@ namespace Avalonia.X11
ClientMessageEvent =
{
type = XEventName.ClientMessage,
send_event = true,
send_event = 1,
window = _handle,
message_type = message_type,
format = 32,
@ -1130,6 +1116,8 @@ namespace Avalonia.X11
public IPopupPositioner PopupPositioner { get; }
public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
public INativeControlHostImpl NativeControlHost { get; }
public ITextInputMethodImpl TextInputMethod => _ime;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper.SetTransparencyRequest(transparencyLevel);

66
src/Avalonia.X11/XLib.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Platform.Interop;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
@ -57,6 +58,9 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern IntPtr XNextEvent(IntPtr display, out XEvent xevent);
[DllImport(libX11)]
public static extern IntPtr XNextEvent(IntPtr display, XEvent* xevent);
[DllImport(libX11)]
public static extern int XConnectionNumber(IntPtr diplay);
@ -407,6 +411,9 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern bool XFilterEvent(ref XEvent xevent, IntPtr window);
[DllImport(libX11)]
public static extern bool XFilterEvent(XEvent* xevent, IntPtr window);
[DllImport(libX11)]
public static extern void XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, IntPtr supported);
@ -441,9 +448,9 @@ namespace Avalonia.X11
[DllImport(libX11)]
public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create);
public enum XLookupStatus
public enum XLookupStatus : uint
{
XBufferOverflow = -1,
XBufferOverflow = 0xffffffffu,
XLookupNone = 1,
XLookupChars = 2,
XLookupKeySym = 3,
@ -454,7 +461,10 @@ namespace Avalonia.X11
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);
public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out UIntPtr status);
[DllImport (libX11)]
public static extern unsafe int Xutf8LookupString(IntPtr xic, 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);
@ -464,12 +474,52 @@ namespace Avalonia.X11
[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);
public static extern IntPtr XGetIMValues (IntPtr xim, string name, out XIMStyles* value, IntPtr terminator);
[DllImport (libX11)]
public static extern IntPtr XCreateIC (IntPtr xim, string name, IntPtr value, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator);
[DllImport(libX11)]
public static extern IntPtr XCreateIC(IntPtr xim, string name, IntPtr value, string name2, IntPtr value2,
string name3, IntPtr value3, string name4, IntPtr value4, IntPtr terminator);
[DllImport(libX11)]
public static extern IntPtr XCreateIC(IntPtr xim, string xnClientWindow, IntPtr handle,
string xnInputStyle, IntPtr value3, string xnResourceName, string optionsWmClass,
string xnResourceClass, string wmClass, string xnPreeditAttributes, IntPtr list, IntPtr zero);
[DllImport(libX11)]
public static extern IntPtr XCreateIC(IntPtr xim, string xnClientWindow, IntPtr handle, string xnFocusWindow,
IntPtr value2, string xnInputStyle, IntPtr value3, string xnResourceName, string optionsWmClass,
string xnResourceClass, string wmClass, string xnPreeditAttributes, IntPtr list, IntPtr zero);
[DllImport(libX11)]
public static extern void XSetICFocus(IntPtr xic);
[DllImport(libX11)]
public static extern void XUnsetICFocus(IntPtr xic);
[DllImport(libX11)]
public static extern IntPtr XmbResetIC(IntPtr xic);
[DllImport(libX11)]
public static extern IntPtr XVaCreateNestedList(int unused, Utf8Buffer name, ref XPoint point, IntPtr terminator);
[DllImport(libX11)]
public static extern IntPtr XVaCreateNestedList(int unused, Utf8Buffer xnArea, XRectangle* point,
Utf8Buffer xnSpotLocation, XPoint* value2, Utf8Buffer xnFontSet, IntPtr fs, IntPtr zero);
[DllImport(libX11)]
public static extern IntPtr XVaCreateNestedList(int unused,
Utf8Buffer xnSpotLocation, XPoint* value2, Utf8Buffer xnFontSet, IntPtr fs, IntPtr zero);
[DllImport (libX11)]
public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator);
public static extern IntPtr XCreateFontSet (IntPtr display, string name, out IntPtr list, out int count, IntPtr unused);
[DllImport(libX11)]
public static extern IntPtr XSetICValues(IntPtr ic, string name, IntPtr data, IntPtr terminator);
[DllImport (libX11)]
public static extern void XCloseIM (IntPtr xim);
@ -633,14 +683,12 @@ namespace Avalonia.X11
}
}
public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, Action<XEvent> handler)
public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, X11PlatformThreading.EventHandler handler)
{
var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow,
0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);
plat.Windows[win] = handler;
return win;
}
}
}

14
src/Shared/PlatformSupport/DynLoader.cs

@ -11,13 +11,25 @@ namespace Avalonia.Shared.PlatformSupport
// ReSharper disable InconsistentNaming
static class LinuxImports
{
#if __ANDROID__
[DllImport("libdl.so")]
#else
[DllImport("libdl.so.2")]
#endif
private static extern IntPtr dlopen(string path, int flags);
#if __ANDROID__
[DllImport("libdl.so")]
#else
[DllImport("libdl.so.2")]
#endif
private static extern IntPtr dlsym(IntPtr handle, string symbol);
#if __ANDROID__
[DllImport("libdl.so")]
#else
[DllImport("libdl.so.2")]
#endif
private static extern IntPtr dlerror();
public static void Init()
@ -27,7 +39,7 @@ namespace Avalonia.Shared.PlatformSupport
DlError = dlerror;
}
}
static class OsXImports
{

20
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -49,12 +49,20 @@ namespace Avalonia.Shared.PlatformSupport
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
{
if (size <= 0)
throw new ArgumentException("Positive number required", nameof(size));
_plat = plat;
_address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
try
{
if (size <= 0)
throw new ArgumentException("Positive number required", nameof(size));
_plat = plat;
_address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
}
catch
{
GC.SuppressFinalize(this);
throw;
}
#if DEBUG
_backtrace = Environment.StackTrace;
lock (_btlock)

11
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -109,20 +109,23 @@ namespace Avalonia.Skia
if (typeface.FontFamily.Key == null)
{
var defaultName = SKTypeface.Default.FamilyName;
var fontStyle = new SKFontStyle((SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
skTypeface = SKTypeface.FromFamilyName(familyName, (SKFontStyleWeight)typeface.Weight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
skTypeface = _skFontManager.MatchFamily(familyName, fontStyle);
if (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal) &&
defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal))
if (skTypeface is null
|| (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal)
&& defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal)))
{
continue;
}
break;
}
skTypeface ??= _skFontManager.MatchTypeface(SKTypeface.Default, fontStyle);
}
else
{

8
src/Windows/Avalonia.Win32/Win32GlManager.cs

@ -9,6 +9,8 @@ namespace Avalonia.Win32
{
static class Win32GlManager
{
private static readonly Version Windows7 = new Version(6, 1);
public static void Initialize()
{
AvaloniaLocator.CurrentMutable.Bind<IPlatformOpenGlInterface>().ToLazy<IPlatformOpenGlInterface>(() =>
@ -20,13 +22,11 @@ namespace Avalonia.Win32
return wgl;
}
if (opts?.AllowEglInitialization == true ||
((!opts?.AllowEglInitialization.HasValue ?? false) &&
Win32Platform.WindowsVersion > new Version(6, 1)))
if (opts?.AllowEglInitialization ?? Win32Platform.WindowsVersion > Windows7)
{
var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
if (egl is { } &&
if (egl != null &&
opts?.UseWindowsUIComposition == true)
{
WinUICompositorConnection.TryCreateAndRegister(egl);

2
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs

@ -5,7 +5,7 @@ using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MakrupExtensions
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class ResourceIncludeTests : XamlTestBase
{

2
tests/Avalonia.Markup.Xaml.UnitTests/StyleIncludeTests.cs

@ -12,7 +12,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
{
var styleXaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<StaticResource x:Key='brush' ResourceKey='missing' />
</Style.Resources>

Loading…
Cancel
Save