diff --git a/Documentation/build.md b/Documentation/build.md
index 9f5436e68e..ddd38be887 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -1,6 +1,6 @@
# Windows
-Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on Windows.
+Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms.
### Clone the Avalonia repository
@@ -16,7 +16,7 @@ Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the l
### Open in Visual Studio
-Open the `Avalonia.sln` solution in Visual Studio 2019 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
+Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
### Troubleshooting
@@ -43,27 +43,6 @@ Go to https://www.microsoft.com/net/core and follow the instructions for your OS
The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install.
-Linux operating systems ship with their own respective package managers however we will use [Homebrew](https://brew.sh/) to manage packages on macOS. To install follow the instructions [here](https://docs.brew.sh/Installation).
-
-### Install CastXML (pre Nov 2020)
-
-Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processing during the build process. The easiest way to install this is via the operating system's package managers, such as below.
-
-On macOS:
-```
-brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
-```
-
-On Debian based Linux (Debian, Ubuntu, Mint, etc):
-```
-sudo apt install castxml
-```
-
-On Red Hat based Linux (Fedora, CentOS, RHEL, etc) using `yum` (`dnf` takes same arguments though):
-```
-sudo yum install castxml
-```
-
### Clone the Avalonia repository
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 60211292c4..5ec856be9e 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -219,7 +219,11 @@ public:
auto window = Window;
Window = nullptr;
- [window close];
+ try{
+ // Seems to throw sometimes on application exit.
+ [window close];
+ }
+ catch(NSException*){}
}
return S_OK;
@@ -737,6 +741,7 @@ private:
if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
+ [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
UpdateStyle();
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 3f9ccb04eb..d43a5c1624 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -88,6 +88,7 @@
$(IntermediateOutputPath)/Avalonia/references
$(IntermediateOutputPath)/Avalonia/original.dll
false
+ false
+ public Task RunAsync(Animatable control, IClock clock = null)
+ {
+ return RunAsync(control, clock, default);
+ }
+
///
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default)
{
diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs
index d784227620..23afa76bf6 100644
--- a/src/Avalonia.Animation/Animators/Animator`1.cs
+++ b/src/Avalonia.Animation/Animators/Animator`1.cs
@@ -79,15 +79,15 @@ namespace Avalonia.Animation.Animators
T oldValue, newValue;
- if (firstKeyframe.isNeutral)
- oldValue = neutralValue;
+ if (!firstKeyframe.isNeutral && firstKeyframe.Value is T firstKeyframeValue)
+ oldValue = firstKeyframeValue;
else
- oldValue = (T)firstKeyframe.Value;
+ oldValue = neutralValue;
- if (lastKeyframe.isNeutral)
- newValue = neutralValue;
+ if (!lastKeyframe.isNeutral && lastKeyframe.Value is T lastKeyframeValue)
+ newValue = lastKeyframeValue;
else
- newValue = (T)lastKeyframe.Value;
+ newValue = neutralValue;
if (lastKeyframe.KeySpline != null)
progress = lastKeyframe.KeySpline.GetSplineProgress(progress);
diff --git a/src/Avalonia.Animation/ApiCompatBaseline.txt b/src/Avalonia.Animation/ApiCompatBaseline.txt
index 58cb7830e7..973698f872 100644
--- a/src/Avalonia.Animation/ApiCompatBaseline.txt
+++ b/src/Avalonia.Animation/ApiCompatBaseline.txt
@@ -1,6 +1,5 @@
Compat issues with assembly Avalonia.Animation:
-MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
-Total Issues: 4
+Total Issues: 3
diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs
index 93948e54ee..e8c84ffb21 100644
--- a/src/Avalonia.Base/Data/BindingValue.cs
+++ b/src/Avalonia.Base/Data/BindingValue.cs
@@ -247,7 +247,7 @@ namespace Avalonia.Data
UnsetValueType _ => Unset,
DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast(),
- _ => new BindingValue((T)value)
+ _ => new BindingValue((T?)value)
};
}
diff --git a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
index 7ff0a8ceca..251dbb458d 100644
--- a/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
+++ b/src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
@@ -140,18 +140,9 @@ namespace Avalonia.Data.Converters
);
}
- Action action = null;
- try
- {
- action = Expression
- .Lambda>(body, parameter)
- .Compile();
- }
- catch (Exception ex)
- {
- throw ex;
- }
- return action;
+ return Expression
+ .Lambda>(body, parameter)
+ .Compile();
}
static Func CreateCanExecute(object target
@@ -170,7 +161,7 @@ namespace Avalonia.Data.Converters
.Compile();
}
- private static Expression? ConvertTarget(object? target, MethodInfo method) =>
+ private static Expression ConvertTarget(object target, MethodInfo method) =>
target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType);
internal class WeakPropertyChangedProxy
diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
index b85991fb77..62a9ed27be 100644
--- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
@@ -1,9 +1,6 @@
using System;
-using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Reflection;
-using System.Threading;
using Microsoft.Build.Framework;
namespace Avalonia.Build.Tasks
@@ -41,7 +38,7 @@ namespace Avalonia.Build.Tasks
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl, outputImportance,
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
- EnableComInteropPatching, SkipXamlCompilation);
+ EnableComInteropPatching, SkipXamlCompilation, DebuggerLaunch);
if (!res.Success)
return false;
if (!res.WrittenFile)
@@ -87,5 +84,7 @@ namespace Avalonia.Build.Tasks
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
+
+ public bool DebuggerLaunch { get; set; }
}
}
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 508045dccb..593d79471e 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -1,13 +1,10 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Microsoft.Build.Framework;
using Mono.Cecil;
-using Avalonia.Utilities;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using XamlX;
@@ -44,16 +41,23 @@ namespace Avalonia.Build.Tasks
string projectDirectory,
string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom,
bool skipXamlCompilation)
+ {
+ return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, patchCom, skipXamlCompilation, debuggerLaunch:false);
+ }
+
+ internal static CompileResult Compile(IBuildEngine engine, string input, string[] references,
+ string projectDirectory,
+ string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch)
{
var typeSystem = new CecilTypeSystem(references
.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll"))
.Concat(new[] { input }), input);
-
+
var asm = typeSystem.TargetAssemblyDefinition;
if (!skipXamlCompilation)
{
- var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance);
+ var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch);
if (compileRes == null && !patchCom)
return new CompileResult(true);
if (compileRes == false)
@@ -62,7 +66,7 @@ namespace Avalonia.Build.Tasks
if (patchCom)
ComInteropHelper.PatchAssembly(asm, typeSystem);
-
+
var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
if (!string.IsNullOrWhiteSpace(strongNameKey))
writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
@@ -70,13 +74,43 @@ namespace Avalonia.Build.Tasks
asm.Write(output, writerParameters);
return new CompileResult(true, true);
-
+
}
-
+
static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem,
string projectDirectory, bool verifyIl,
- MessageImportance logImportance)
+ MessageImportance logImportance
+ , bool debuggerLaunch = false)
{
+ if (debuggerLaunch)
+ {
+ // According this https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debugger.launch?view=net-6.0#remarks
+ // documentation, on not windows platform Debugger.Launch() always return true without running a debugger.
+ if (System.Diagnostics.Debugger.Launch())
+ {
+ // Set timeout at 1 minut.
+ var time = new System.Diagnostics.Stopwatch();
+ var timeout = TimeSpan.FromMinutes(1);
+ time.Start();
+
+ // wait for the debugger to be attacked or timeout.
+ while (!System.Diagnostics.Debugger.IsAttached && time.Elapsed < timeout)
+ {
+ engine.LogMessage($"[PID:{System.Diagnostics.Process.GetCurrentProcess().Id}] Wating attach debugger. Elapsed {time.Elapsed}...", MessageImportance.High);
+ System.Threading.Thread.Sleep(100);
+ }
+
+ time.Stop();
+ if (time.Elapsed >= timeout)
+ {
+ engine.LogMessage("Wating attach debugger timeout.", MessageImportance.Normal);
+ }
+ }
+ else
+ {
+ engine.LogMessage("Debugging cancelled.", MessageImportance.Normal);
+ }
+ }
var asm = typeSystem.TargetAssemblyDefinition;
var emres = new EmbeddedResources(asm);
var avares = new AvaloniaResources(asm, projectDirectory);
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 157bebe02b..df55d9e250 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -104,7 +104,7 @@ namespace Avalonia
///
/// The application's focus manager.
///
- public IFocusManager FocusManager
+ public IFocusManager? FocusManager
{
get;
private set;
@@ -116,7 +116,7 @@ namespace Avalonia
///
/// The application's input manager.
///
- public InputManager InputManager
+ public InputManager? InputManager
{
get;
private set;
@@ -175,7 +175,7 @@ namespace Avalonia
/// -
/// -
///
- public IApplicationLifetime ApplicationLifetime { get; set; }
+ public IApplicationLifetime? ApplicationLifetime { get; set; }
event Action> IGlobalStyles.GlobalStylesAdded
{
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index 2a42d99ac5..3a2fd68af5 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@@ -122,12 +123,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_cts = new CancellationTokenSource();
- MainWindow?.Show();
+
+ // Note due to a bug in the JIT we wrap this in a method, otherwise MainWindow
+ // gets stuffed into a local var and can not be GCed until after the program stops.
+ // this method never exits until program end.
+ ShowMainWindow();
+
Dispatcher.UIThread.MainLoop(_cts.Token);
Environment.ExitCode = _exitCode;
return _exitCode;
}
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void ShowMainWindow()
+ {
+ MainWindow?.Show();
+ }
+
public void Dispose()
{
if (_activeLifetime == this)
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 4b903d056c..b874d149bf 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -562,8 +562,12 @@ namespace Avalonia.Controls.Primitives
return eventArgs.Cancel;
}
- internal static void SetPresenterClasses(IControl presenter, Classes classes)
+ internal static void SetPresenterClasses(IControl? presenter, Classes classes)
{
+ if(presenter is null)
+ {
+ return;
+ }
//Remove any classes no longer in use, ignoring pseudo classes
for (int i = presenter.Classes.Count - 1; i >= 0; i--)
{
diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
index a5495fdfc9..6a95c2e047 100644
--- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
+++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
@@ -85,7 +85,9 @@ namespace Avalonia.Controls.Platform
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action Signaled;
+#pragma warning disable CS0067
public event Action Tick;
+#pragma warning restore CS0067
}
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index c8ae428db3..0f4df856ec 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -94,8 +94,8 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty OverlayDismissEventPassThroughProperty =
AvaloniaProperty.Register(nameof(OverlayDismissEventPassThrough));
- public static readonly DirectProperty OverlayInputPassThroughElementProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly DirectProperty OverlayInputPassThroughElementProperty =
+ AvaloniaProperty.RegisterDirect(
nameof(OverlayInputPassThroughElement),
o => o.OverlayInputPassThroughElement,
(o, v) => o.OverlayInputPassThroughElement = v);
@@ -139,7 +139,7 @@ namespace Avalonia.Controls.Primitives
private bool _isOpen;
private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState;
- private IInputElement _overlayInputPassThroughElement;
+ private IInputElement? _overlayInputPassThroughElement;
private Action? _popupHostChangedHandler;
///
@@ -311,7 +311,7 @@ namespace Avalonia.Controls.Primitives
/// Gets or sets an element that should receive pointer input events even when underneath
/// the popup's overlay.
///
- public IInputElement OverlayInputPassThroughElement
+ public IInputElement? OverlayInputPassThroughElement
{
get => _overlayInputPassThroughElement;
set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);
diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs
index 4f5a7cd311..419792f004 100644
--- a/src/Avalonia.Controls/Remote/RemoteServer.cs
+++ b/src/Avalonia.Controls/Remote/RemoteServer.cs
@@ -15,9 +15,6 @@ namespace Avalonia.Controls.Remote
public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport)
{
}
-#pragma warning disable 67
- public Action LostFocus { get; set; }
-
}
public RemoteServer(IAvaloniaRemoteTransportConnection transport)
diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs
index 0e35c610b2..6a0d4e2023 100644
--- a/src/Avalonia.Controls/SplitView.cs
+++ b/src/Avalonia.Controls/SplitView.cs
@@ -129,14 +129,14 @@ namespace Avalonia.Controls
///
/// Defines the property
///
- public static readonly StyledProperty PaneProperty =
- AvaloniaProperty.Register(nameof(Pane));
+ public static readonly StyledProperty PaneProperty =
+ AvaloniaProperty.Register(nameof(Pane));
///
/// Defines the property.
///
- public static readonly StyledProperty PaneTemplateProperty =
- AvaloniaProperty.Register(nameof(PaneTemplate));
+ public static readonly StyledProperty PaneTemplateProperty =
+ AvaloniaProperty.Register(nameof(PaneTemplate));
///
/// Defines the property
@@ -267,7 +267,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the data template used to display the header content of the control.
///
- public IDataTemplate? PaneTemplate
+ public IDataTemplate PaneTemplate
{
get => GetValue(PaneTemplateProperty);
set => SetValue(PaneTemplateProperty, value);
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index e8122dd311..c5a729afae 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Controls
public bool SupportsSurroundingText => false;
public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException();
- public event EventHandler SurroundingTextChanged;
+ public event EventHandler SurroundingTextChanged { add { } remove { } }
public string TextBeforeCursor => null;
public string TextAfterCursor => null;
diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs
index 59edb6278a..9fbc2fc8b2 100644
--- a/src/Avalonia.Controls/TrayIcon.cs
+++ b/src/Avalonia.Controls/TrayIcon.cs
@@ -140,7 +140,7 @@ namespace Avalonia.Controls
/// Gets or sets the parameter to pass to the property of a
/// .
///
- public object CommandParameter
+ public object? CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index a3fd584447..214d77c7ae 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -194,6 +194,12 @@ namespace Avalonia.Controls
try
{
IsVisible = false;
+
+ if (this is IFocusScope scope)
+ {
+ FocusManager.Instance?.RemoveFocusScope(scope);
+ }
+
base.HandleClosed();
}
finally
diff --git a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
index 0448a5c05d..45a6c97954 100644
--- a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
+++ b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
@@ -59,7 +59,7 @@ namespace Avalonia.DesignerSupport.Remote
remove { _onMessage -= value; }
}
- public event Action OnException;
+ public event Action OnException { add { } remove { } }
public void Start()
{
UpdaterThread();
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index 7b32e21fbd..9905aa4afc 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -17,16 +17,16 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{
private readonly IVisual _control;
- private IDictionary> _propertyIndex;
+ private IDictionary>? _propertyIndex;
private PropertyViewModel? _selectedProperty;
- private DataGridCollectionView _propertiesView;
+ private DataGridCollectionView? _propertiesView;
private bool _snapshotStyles;
private bool _showInactiveStyles;
private string? _styleStatus;
- private object _selectedEntity;
+ private object? _selectedEntity;
private readonly Stack<(string Name,object Entry)> _selectedEntitiesStack = new();
- private string _selectedEntityName;
- private string _selectedEntityType;
+ private string? _selectedEntityName;
+ private string? _selectedEntityType;
public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control)
{
@@ -117,7 +117,7 @@ namespace Avalonia.Diagnostics.ViewModels
public TreePageViewModel TreePage { get; }
- public DataGridCollectionView PropertiesView
+ public DataGridCollectionView? PropertiesView
{
get => _propertiesView;
private set => RaiseAndSetIfChanged(ref _propertiesView, value);
@@ -127,7 +127,7 @@ namespace Avalonia.Diagnostics.ViewModels
public ObservableCollection PseudoClasses { get; }
- public object SelectedEntity
+ public object? SelectedEntity
{
get => _selectedEntity;
set
@@ -137,7 +137,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public string SelectedEntityName
+ public string? SelectedEntityName
{
get => _selectedEntityName;
set
@@ -147,7 +147,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public string SelectedEntityType
+ public string? SelectedEntityType
{
get => _selectedEntityType;
set
@@ -270,7 +270,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- if (_propertyIndex.TryGetValue(e.Property, out var properties))
+ if (_propertyIndex is { } && _propertyIndex.TryGetValue(e.Property, out var properties))
{
foreach (var property in properties)
{
@@ -284,6 +284,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != null
+ && _propertyIndex is { }
&& _propertyIndex.TryGetValue(e.PropertyName, out var properties))
{
foreach (var property in properties)
@@ -402,7 +403,7 @@ namespace Avalonia.Diagnostics.ViewModels
var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity;
var selectedEntityName = SelectedEntityName;
- if (selectedProperty == null)
+ if (selectedEntity == null || selectedProperty == null)
return;
object? property;
@@ -419,7 +420,7 @@ namespace Avalonia.Diagnostics.ViewModels
?.GetValue(selectedEntity);
}
if (property == null) return;
- _selectedEntitiesStack.Push((Name:selectedEntityName,Entry:selectedEntity));
+ _selectedEntitiesStack.Push((Name:selectedEntityName!,Entry:selectedEntity));
NavigateToProperty(property, selectedProperty.Name);
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
index 4b18cf414a..27b28f35fc 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Diagnostics.ViewModels
Nodes = nodes;
PropertiesFilter = new FilterViewModel();
- PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView.Refresh();
+ PropertiesFilter.RefreshFilter += (s, e) => Details?.PropertiesView?.Refresh();
SettersFilter = new FilterViewModel();
SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters();
diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
index 9e426688d8..206c24ad5e 100644
--- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
+++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs
@@ -413,10 +413,10 @@ namespace Avalonia.FreeDesktop
#region Events
private event Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)>
- ItemsPropertiesUpdated;
+ ItemsPropertiesUpdated { add { } remove { } }
private event Action<(uint revision, int parent)> LayoutUpdated;
- private event Action<(int id, uint timestamp)> ItemActivationRequested;
- private event Action PropertiesChanged;
+ private event Action<(int id, uint timestamp)> ItemActivationRequested { add { } remove { } }
+ private event Action PropertiesChanged { add { } remove { } }
async Task IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action onError)
{
diff --git a/src/Avalonia.Input/ApiCompatBaseline.txt b/src/Avalonia.Input/ApiCompatBaseline.txt
index 98eb8598d8..270c5305e5 100644
--- a/src/Avalonia.Input/ApiCompatBaseline.txt
+++ b/src/Avalonia.Input/ApiCompatBaseline.txt
@@ -3,6 +3,7 @@ MembersMustExist : Member 'public Avalonia.Platform.IPlatformHandle Avalonia.Inp
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Input.Gestures.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Input.Gestures.RightTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Input.Gestures.TappedEvent' does not exist in the implementation but it does exist in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.IFocusManager.RemoveFocusScope(Avalonia.Input.IFocusScope)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Input.InputElement.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent Avalonia.Interactivity.RoutedEvent Avalonia.Input.InputElement.TappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_DoubleTapped(System.EventHandler)' does not exist in the implementation but it does exist in the contract.
@@ -10,4 +11,4 @@ MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_Tapped(Sy
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_DoubleTapped(System.EventHandler)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_Tapped(System.EventHandler)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract.
-Total Issues: 11
+Total Issues: 12
diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs
index 1432092ba1..2efd33df1d 100644
--- a/src/Avalonia.Input/FocusManager.cs
+++ b/src/Avalonia.Input/FocusManager.cs
@@ -162,6 +162,22 @@ namespace Avalonia.Input
Focus(e);
}
+ public void RemoveFocusScope(IFocusScope scope)
+ {
+ scope = scope ?? throw new ArgumentNullException(nameof(scope));
+
+ if (_focusScopes.TryGetValue(scope, out _))
+ {
+ SetFocusedElement(scope, null);
+ _focusScopes.Remove(scope);
+ }
+
+ if (Scope == scope)
+ {
+ Scope = null;
+ }
+ }
+
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
///
diff --git a/src/Avalonia.Input/ICommandSource.cs b/src/Avalonia.Input/ICommandSource.cs
index eed71759d5..410b3a2e47 100644
--- a/src/Avalonia.Input/ICommandSource.cs
+++ b/src/Avalonia.Input/ICommandSource.cs
@@ -1,5 +1,5 @@
using System.Windows.Input;
-
+#nullable enable
namespace Avalonia.Input
{
///
@@ -12,13 +12,13 @@ namespace Avalonia.Input
/// Classes that implement this interface should enable or disable based on the command's CanExecute return value.
/// The property may be implemented as read-write if desired.
///
- ICommand Command { get; }
+ ICommand? Command { get; }
///
/// The parameter that will be passed to the command when executing the command.
/// The property may be implemented as read-write if desired.
///
- object CommandParameter { get; }
+ object? CommandParameter { get; }
///
diff --git a/src/Avalonia.Input/IFocusManager.cs b/src/Avalonia.Input/IFocusManager.cs
index e1b5087c3d..2510479a8e 100644
--- a/src/Avalonia.Input/IFocusManager.cs
+++ b/src/Avalonia.Input/IFocusManager.cs
@@ -35,5 +35,13 @@ namespace Avalonia.Input
/// when it activates, e.g. when a Window is activated.
///
void SetFocusScope(IFocusScope scope);
+
+ ///
+ /// Notifies the focus manager that a focus scope has been removed.
+ ///
+ /// The focus scope to be removed.
+ /// This should not be called by client code. It is called by an
+ /// when it deactivates or closes, e.g. when a Window is closed.
+ void RemoveFocusScope(IFocusScope scope);
}
}
diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
index 4431e108ed..1582f794ae 100644
--- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
+++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
@@ -44,7 +44,7 @@ namespace Avalonia.Native
public bool IsNativeMenuExported => _exported;
- public event EventHandler OnIsNativeMenuExportedChanged;
+ public event EventHandler OnIsNativeMenuExportedChanged { add { } remove { } }
public void SetNativeMenu(NativeMenu menu)
{
diff --git a/src/Avalonia.Styling/Styling/PropertySetterInstance.cs b/src/Avalonia.Styling/Styling/PropertySetterInstance.cs
index 1c3055fed6..bb0e342df6 100644
--- a/src/Avalonia.Styling/Styling/PropertySetterInstance.cs
+++ b/src/Avalonia.Styling/Styling/PropertySetterInstance.cs
@@ -16,14 +16,14 @@ namespace Avalonia.Styling
private readonly IStyleable _target;
private readonly StyledPropertyBase? _styledProperty;
private readonly DirectPropertyBase? _directProperty;
- private readonly T _value;
+ private readonly T? _value;
private IDisposable? _subscription;
private bool _isActive;
public PropertySetterInstance(
IStyleable target,
StyledPropertyBase property,
- T value)
+ T? value)
{
_target = target;
_styledProperty = property;
@@ -57,7 +57,7 @@ namespace Avalonia.Styling
{
if (_styledProperty is object)
{
- _subscription = _target.SetValue(_styledProperty, _value, BindingPriority.Style);
+ _subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style);
}
else
{
diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs
index 1f3d6335a9..4c94ea02dd 100644
--- a/src/Avalonia.Styling/Styling/Setter.cs
+++ b/src/Avalonia.Styling/Styling/Setter.cs
@@ -101,7 +101,7 @@ namespace Avalonia.Styling
data.result = new PropertySetterInstance(
data.target,
property,
- (T)data.value);
+ (T?)data.value);
}
}
@@ -128,7 +128,7 @@ namespace Avalonia.Styling
data.result = new PropertySetterInstance(
data.target,
property,
- (T)data.value);
+ (T)data.value!);
}
}
diff --git a/src/Avalonia.Themes.Default/Expander.xaml b/src/Avalonia.Themes.Default/Expander.xaml
index 7df65677b6..e72ddea163 100644
--- a/src/Avalonia.Themes.Default/Expander.xaml
+++ b/src/Avalonia.Themes.Default/Expander.xaml
@@ -15,7 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+ /// Appends another matrix as post-multiplication operation.
+ /// Equivalent to this * value;
+ ///
+ /// A matrix.
+ /// Post-multiplied matrix.
+ public Matrix Append(Matrix value)
+ {
+ return this * value;
+ }
+
+ ///
+ /// Prpends another matrix as pre-multiplication operation.
+ /// Equivalent to value * this;
+ ///
+ /// A matrix.
+ /// Pre-multiplied matrix.
+ public Matrix Prepend(Matrix value)
+ {
+ return value * this;
+ }
+
///
/// Calculates the determinant for this matrix.
///
diff --git a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
index 742bb9c804..76c2deef3b 100644
--- a/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
+++ b/src/Avalonia.Visuals/Media/Transformation/InterpolationUtilities.cs
@@ -18,11 +18,11 @@ namespace Avalonia.Media.Transformation
public static Matrix ComposeTransform(Matrix.Decomposed decomposed)
{
// According to https://www.w3.org/TR/css-transforms-1/#recomposing-to-a-2d-matrix
-
- return Matrix.CreateTranslation(decomposed.Translate) *
- Matrix.CreateRotation(decomposed.Angle) *
- Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y) *
- Matrix.CreateScale(decomposed.Scale);
+ return Matrix.Identity
+ .Prepend(Matrix.CreateTranslation(decomposed.Translate))
+ .Prepend(Matrix.CreateRotation(decomposed.Angle))
+ .Prepend(Matrix.CreateSkew(decomposed.Skew.X, decomposed.Skew.Y))
+ .Prepend(Matrix.CreateScale(decomposed.Scale));
}
public static Matrix.Decomposed InterpolateDecomposedTransforms(ref Matrix.Decomposed from, ref Matrix.Decomposed to, double progress)
diff --git a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs
index 36f5dd98f1..13a24cd523 100644
--- a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs
+++ b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs
@@ -86,6 +86,8 @@ namespace Avalonia.Media.Transformation
if (fromIdentity && toIdentity)
{
+ result.Matrix = Matrix.Identity;
+
return true;
}
@@ -179,7 +181,8 @@ namespace Avalonia.Media.Transformation
}
case OperationType.Identity:
{
- // Do nothing.
+ result.Matrix = Matrix.Identity;
+
break;
}
}
diff --git a/src/Avalonia.X11/ICELib.cs b/src/Avalonia.X11/ICELib.cs
new file mode 100644
index 0000000000..8ef21dd000
--- /dev/null
+++ b/src/Avalonia.X11/ICELib.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.X11
+{
+ internal static class ICELib
+ {
+ private const string LibIce = "libICE.so.6";
+
+ [DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
+ public static extern int IceAddConnectionWatch(
+ IntPtr watchProc,
+ IntPtr clientData
+ );
+
+ [DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
+ public static extern void IceRemoveConnectionWatch(
+ IntPtr watchProc,
+ IntPtr clientData
+ );
+
+ [DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
+ public static extern IceProcessMessagesStatus IceProcessMessages(
+ IntPtr iceConn,
+ out IntPtr replyWait,
+ out bool replyReadyRet
+ );
+
+ [DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
+ public static extern IntPtr IceSetErrorHandler(
+ IntPtr handler
+ );
+
+ [DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
+ public static extern IntPtr IceSetIOErrorHandler(
+ IntPtr handler
+ );
+
+ public enum IceProcessMessagesStatus
+ {
+ IceProcessMessagesIoError = 1
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void IceErrorHandler(
+ IntPtr iceConn,
+ bool swap,
+ int offendingMinorOpcode,
+ ulong offendingSequence,
+ int errorClass,
+ int severity,
+ IntPtr values
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void IceIOErrorHandler(
+ IntPtr iceConn
+ );
+ }
+}
diff --git a/src/Avalonia.X11/SMLib.cs b/src/Avalonia.X11/SMLib.cs
new file mode 100644
index 0000000000..6ffcad60bf
--- /dev/null
+++ b/src/Avalonia.X11/SMLib.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.X11
+{
+ internal static unsafe class SMLib
+ {
+ private const string LibSm = "libSM.so.6";
+
+ [DllImport(LibSm, CharSet = CharSet.Ansi)]
+ public static extern IntPtr SmcOpenConnection(
+ [MarshalAs(UnmanagedType.LPWStr)] string networkId,
+ IntPtr content,
+ int xsmpMajorRev,
+ int xsmpMinorRev,
+ ulong mask,
+ ref SmcCallbacks callbacks,
+ [MarshalAs(UnmanagedType.LPWStr)] [Out]
+ out string previousId,
+ [MarshalAs(UnmanagedType.LPWStr)] [Out]
+ out string clientIdRet,
+ int errorLength,
+ [Out] char[] errorStringRet);
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern int SmcCloseConnection(
+ IntPtr smcConn,
+ int count,
+ string[] reasonMsgs
+ );
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern void SmcSaveYourselfDone(
+ IntPtr smcConn,
+ bool success
+ );
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern int SmcInteractRequest(
+ IntPtr smcConn,
+ SmDialogValue dialogType,
+ IntPtr interactProc,
+ IntPtr clientData
+ );
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern void SmcInteractDone(
+ IntPtr smcConn,
+ bool success
+ );
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern IntPtr SmcGetIceConnection(
+ IntPtr smcConn
+ );
+
+ [DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
+ public static extern IntPtr SmcSetErrorHandler(
+ IntPtr handler
+ );
+
+ public enum SmDialogValue
+ {
+ SmDialogError = 0
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SmcCallbacks
+ {
+ public IntPtr SaveYourself;
+ private readonly IntPtr Unused0;
+ public IntPtr Die;
+ private readonly IntPtr Unused1;
+ public IntPtr SaveComplete;
+ private readonly IntPtr Unused2;
+ public IntPtr ShutdownCancelled;
+ private readonly IntPtr Unused3;
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void IceWatchProc(
+ IntPtr iceConn,
+ IntPtr clientData,
+ bool opening,
+ IntPtr* watchData
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcDieProc(
+ IntPtr smcConn,
+ IntPtr clientData
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcInteractProc(
+ IntPtr smcConn,
+ IntPtr clientData
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcSaveCompleteProc(
+ IntPtr smcConn,
+ IntPtr clientData
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcSaveYourselfProc(
+ IntPtr smcConn,
+ IntPtr clientData,
+ int saveType,
+ bool shutdown,
+ int interactStyle,
+ bool fast
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcShutdownCancelledProc(
+ IntPtr smcConn,
+ IntPtr clientData
+ );
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void SmcErrorHandler(
+ IntPtr smcConn,
+ bool swap,
+ int offendingMinorOpcode,
+ ulong offendingSequence,
+ int errorClass,
+ int severity,
+ IntPtr values
+ );
+ }
+}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 8ff3b4f5e0..a18f91d301 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -80,7 +80,8 @@ namespace Avalonia.X11
.Bind().ToConstant(new PlatformSettingsStub())
.Bind().ToConstant(new X11IconLoader(Info))
.Bind().ToConstant(new GtkSystemDialog())
- .Bind().ToConstant(new LinuxMountedVolumeInfoProvider());
+ .Bind().ToConstant(new LinuxMountedVolumeInfoProvider())
+ .Bind().ToConstant(new X11PlatformLifetimeEvents(this));
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);
@@ -230,7 +231,19 @@ namespace Avalonia
/// on their input devices by using sequences of characters or mouse operations that are natively available on their input devices.
///
public bool? EnableIme { get; set; }
-
+
+ ///
+ /// Determines whether to enable support for the
+ /// X Session Management Protocol.
+ ///
+ ///
+ /// X Session Management Protocol is a standard implemented on most
+ /// Linux systems that uses Xorg. This enables apps to control how they
+ /// can control and/or cancel the pending shutdown requested by the user.
+ ///
+ public bool EnableSessionManagement { get; set; } =
+ Environment.GetEnvironmentVariable("AVALONIA_X11_USE_SESSION_MANAGEMENT") != "0";
+
public IList GlProfiles { get; set; } = new List
{
new GlVersion(GlProfileType.OpenGL, 4, 0),
diff --git a/src/Avalonia.X11/X11PlatformLifetimeEvents.cs b/src/Avalonia.X11/X11PlatformLifetimeEvents.cs
new file mode 100644
index 0000000000..06e1d8879f
--- /dev/null
+++ b/src/Avalonia.X11/X11PlatformLifetimeEvents.cs
@@ -0,0 +1,259 @@
+#nullable enable
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Logging;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.X11
+{
+ internal unsafe class X11PlatformLifetimeEvents : IDisposable, IPlatformLifetimeEventsImpl
+ {
+ private readonly AvaloniaX11Platform _platform;
+ private const ulong SmcSaveYourselfProcMask = 1L;
+ private const ulong SmcDieProcMask = 2L;
+ private const ulong SmcSaveCompleteProcMask = 4L;
+ private const ulong SmcShutdownCancelledProcMask = 8L;
+
+ private static readonly ConcurrentDictionary s_nativeToManagedMapper =
+ new ConcurrentDictionary();
+
+ private static readonly SMLib.SmcSaveYourselfProc s_saveYourselfProcDelegate = SmcSaveYourselfHandler;
+ private static readonly SMLib.SmcDieProc s_dieDelegate = SmcDieHandler;
+
+ private static readonly SMLib.SmcShutdownCancelledProc
+ s_shutdownCancelledDelegate = SmcShutdownCancelledHandler;
+
+ private static readonly SMLib.SmcSaveCompleteProc s_saveCompleteDelegate = SmcSaveCompleteHandler;
+ private static readonly SMLib.SmcInteractProc s_smcInteractDelegate = StaticInteractHandler;
+ private static readonly SMLib.SmcErrorHandler s_smcErrorHandlerDelegate = StaticErrorHandler;
+ private static readonly ICELib.IceErrorHandler s_iceErrorHandlerDelegate = StaticErrorHandler;
+ private static readonly ICELib.IceIOErrorHandler s_iceIoErrorHandlerDelegate = StaticIceIOErrorHandler;
+ private static readonly SMLib.IceWatchProc s_iceWatchProcDelegate = IceWatchHandler;
+
+ private static SMLib.SmcCallbacks s_callbacks = new SMLib.SmcCallbacks()
+ {
+ ShutdownCancelled = Marshal.GetFunctionPointerForDelegate(s_shutdownCancelledDelegate),
+ Die = Marshal.GetFunctionPointerForDelegate(s_dieDelegate),
+ SaveYourself = Marshal.GetFunctionPointerForDelegate(s_saveYourselfProcDelegate),
+ SaveComplete = Marshal.GetFunctionPointerForDelegate(s_saveCompleteDelegate)
+ };
+
+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private readonly IntPtr _currentIceConn;
+ private readonly IntPtr _currentSmcConn;
+
+ private bool _saveYourselfPhase;
+
+ internal X11PlatformLifetimeEvents(AvaloniaX11Platform platform)
+ {
+ _platform = platform;
+
+ if (ICELib.IceAddConnectionWatch(
+ Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
+ IntPtr.Zero) == 0)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
+ "SMLib was unable to add an ICE connection watcher.");
+ return;
+ }
+
+ var errorBuf = new char[255];
+
+ var smcConn = SMLib.SmcOpenConnection(null!,
+ IntPtr.Zero, 1, 0,
+ SmcSaveYourselfProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask |
+ SmcDieProcMask,
+ ref s_callbacks,
+ out _,
+ out _,
+ errorBuf.Length,
+ errorBuf);
+
+ if (smcConn == IntPtr.Zero)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
+ $"SMLib/ICELib reported a new error: {new string(errorBuf)}");
+ return;
+ }
+
+ if (!s_nativeToManagedMapper.TryAdd(smcConn, this))
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
+ "SMLib was unable to add this instance to the native to managed map.");
+ return;
+ }
+
+ _ = SMLib.SmcSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_smcErrorHandlerDelegate));
+ _ = ICELib.IceSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceErrorHandlerDelegate));
+ _ = ICELib.IceSetIOErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceIoErrorHandlerDelegate));
+
+ _currentSmcConn = smcConn;
+ _currentIceConn = SMLib.SmcGetIceConnection(smcConn);
+
+ Task.Run(() =>
+ {
+ var token = _cancellationTokenSource.Token;
+ while (!token.IsCancellationRequested) HandleRequests();
+ }, _cancellationTokenSource.Token);
+ }
+
+ public void Dispose()
+ {
+ if (_currentSmcConn == IntPtr.Zero) return;
+
+ s_nativeToManagedMapper.TryRemove(_currentSmcConn, out _);
+
+ _ = SMLib.SmcCloseConnection(_currentSmcConn, 1,
+ new[] { $"{nameof(X11PlatformLifetimeEvents)} was disposed in managed code." });
+ }
+
+ private static void SmcSaveCompleteHandler(IntPtr smcConn, IntPtr clientData)
+ {
+ GetInstance(smcConn)?.SaveCompleteHandler();
+ }
+
+ private static X11PlatformLifetimeEvents? GetInstance(IntPtr smcConn)
+ {
+ return s_nativeToManagedMapper.TryGetValue(smcConn, out var instance) ? instance : null;
+ }
+
+ private static void SmcShutdownCancelledHandler(IntPtr smcConn, IntPtr clientData)
+ {
+ GetInstance(smcConn)?.ShutdownCancelledHandler();
+ }
+
+ private static void SmcDieHandler(IntPtr smcConn, IntPtr clientData)
+ {
+ GetInstance(smcConn)?.DieHandler();
+ }
+
+ private static void SmcSaveYourselfHandler(IntPtr smcConn, IntPtr clientData, int saveType,
+ bool shutdown, int interactStyle, bool fast)
+ {
+ GetInstance(smcConn)?.SaveYourselfHandler(smcConn, clientData, shutdown, fast);
+ }
+
+ private static void StaticInteractHandler(IntPtr smcConn, IntPtr clientData)
+ {
+ GetInstance(smcConn)?.InteractHandler(smcConn);
+ }
+
+ private static void StaticIceIOErrorHandler(IntPtr iceConn)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(null,
+ "ICELib reported an unknown IO Error.");
+ }
+
+ private static void StaticErrorHandler(IntPtr smcConn, bool swap, int offendingMinorOpcode,
+ ulong offendingSequence, int errorClass, int severity, IntPtr values)
+ {
+ GetInstance(smcConn)
+ ?.ErrorHandler(swap, offendingMinorOpcode, offendingSequence, errorClass, severity, values);
+ }
+
+ // ReSharper disable UnusedParameter.Local
+ private void ErrorHandler(bool swap, int offendingMinorOpcode, ulong offendingSequence, int errorClass,
+ int severity, IntPtr values)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
+ "SMLib reported an error:" +
+ $" severity {severity:X}" +
+ $" mOpcode {offendingMinorOpcode:X}" +
+ $" mSeq {offendingSequence:X}" +
+ $" errClass {errorClass:X}.");
+ }
+
+ private void HandleRequests()
+ {
+ if (ICELib.IceProcessMessages(_currentIceConn, out _, out _) ==
+ ICELib.IceProcessMessagesStatus.IceProcessMessagesIoError)
+ {
+ Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
+ "SMLib lost its underlying ICE connection.");
+ Dispose();
+ }
+ }
+
+ private void SaveCompleteHandler()
+ {
+ _saveYourselfPhase = false;
+ }
+
+ private void ShutdownCancelledHandler()
+ {
+ if (_saveYourselfPhase)
+ SMLib.SmcSaveYourselfDone(_currentSmcConn, true);
+ _saveYourselfPhase = false;
+ }
+
+ private void DieHandler()
+ {
+ Dispose();
+ }
+
+ private void SaveYourselfHandler(IntPtr smcConn, IntPtr clientData, bool shutdown, bool fast)
+ {
+ if (_saveYourselfPhase)
+ {
+ SMLib.SmcSaveYourselfDone(smcConn, true);
+ }
+
+ _saveYourselfPhase = true;
+
+ if (shutdown && !fast)
+ {
+ var _ = SMLib.SmcInteractRequest(smcConn, SMLib.SmDialogValue.SmDialogError,
+ Marshal.GetFunctionPointerForDelegate(s_smcInteractDelegate),
+ clientData);
+ }
+ else
+ {
+ SMLib.SmcSaveYourselfDone(smcConn, true);
+ _saveYourselfPhase = false;
+ }
+ }
+
+ private void InteractHandler(IntPtr smcConn)
+ {
+ Dispatcher.UIThread.Post(() => ActualInteractHandler(smcConn));
+ }
+
+ private void ActualInteractHandler(IntPtr smcConn)
+ {
+ var e = new ShutdownRequestedEventArgs();
+
+ if (_platform.Options?.EnableSessionManagement ?? false)
+ {
+ ShutdownRequested?.Invoke(this, e);
+ }
+
+ SMLib.SmcInteractDone(smcConn, e.Cancel);
+
+ if (e.Cancel)
+ {
+ return;
+ }
+
+ _saveYourselfPhase = false;
+
+ SMLib.SmcSaveYourselfDone(smcConn, true);
+ }
+
+ private static void IceWatchHandler(IntPtr iceConn, IntPtr clientData, bool opening, IntPtr* watchData)
+ {
+ if (!opening) return;
+
+ ICELib.IceRemoveConnectionWatch(Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
+ IntPtr.Zero);
+ }
+
+ public event EventHandler? ShutdownRequested;
+ }
+}
diff --git a/src/Avalonia.X11/X11Window.Xim.cs b/src/Avalonia.X11/X11Window.Xim.cs
index 444c82fd22..ecb23ff097 100644
--- a/src/Avalonia.X11/X11Window.Xim.cs
+++ b/src/Avalonia.X11/X11Window.Xim.cs
@@ -112,8 +112,8 @@ namespace Avalonia.X11
public ValueTask HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode) =>
new ValueTask(false);
- public event Action Commit;
- public event Action ForwardKey;
+ public event Action Commit { add { } remove { } }
+ public event Action ForwardKey { add { } remove { } }
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 75ac0ffe96..4ec2197e2c 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -1027,6 +1027,7 @@ namespace Avalonia.X11
if (string.IsNullOrEmpty(title))
{
XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME);
+ XDeleteProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_NAME);
}
else
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
index 4ad68fc63e..db33b88cc3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
@@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
+using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
@@ -33,6 +34,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
_ => null,
};
+ if (provideTarget.TargetObject is Setter setter)
+ {
+ targetType = setter.Property.PropertyType;
+ }
+
// Look upwards though the ambient context for IResourceHosts and IResourceProviders
// which might be able to give us the resource.
foreach (var e in stack.Parents)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 67c4aabafe..d5ebe9d472 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -199,7 +199,6 @@ namespace Avalonia.Win32
case WindowsMessage.WM_MBUTTONUP:
case WindowsMessage.WM_XBUTTONUP:
{
- shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs
index da845b0ecd..28baaa65f8 100644
--- a/src/tools/MicroComGenerator/CSharpGen.Utils.cs
+++ b/src/tools/MicroComGenerator/CSharpGen.Utils.cs
@@ -40,7 +40,7 @@ namespace MicroComGenerator
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
static VariableDeclarationSyntax DeclareVar(string type, string name,
- ExpressionSyntax? initializer = null)
+ ExpressionSyntax initializer = null)
=> VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(VariableDeclarator(name)
.WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));
diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
index b01fb70f58..26d8059eec 100644
--- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
+++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs
@@ -1,5 +1,7 @@
using System;
+using Avalonia.Animation.Animators;
using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Media;
@@ -100,6 +102,71 @@ namespace Avalonia.Animation.UnitTests
Times.Never);
}
+
+ [Theory]
+ [InlineData(null)] //null value
+ [InlineData("stringValue")] //string value
+ public void Invalid_Values_In_Animation_Should_Not_Crash_Animations(object invalidValue)
+ {
+ var keyframe1 = new KeyFrame()
+ {
+ Setters =
+ {
+ new Setter(Layoutable.WidthProperty, 1d),
+ },
+ KeyTime = TimeSpan.FromSeconds(0)
+ };
+
+ var keyframe2 = new KeyFrame()
+ {
+ Setters =
+ {
+ new Setter(Layoutable.WidthProperty, 2d),
+ },
+ KeyTime = TimeSpan.FromSeconds(2),
+ };
+
+ var keyframe3 = new KeyFrame()
+ {
+ Setters =
+ {
+ new Setter(Layoutable.WidthProperty, invalidValue),
+ },
+ KeyTime = TimeSpan.FromSeconds(3),
+ };
+
+ var animation = new Animation()
+ {
+ Duration = TimeSpan.FromSeconds(3),
+ Children =
+ {
+ keyframe1,
+ keyframe2,
+ keyframe3
+ },
+ IterationCount = new IterationCount(5),
+ PlaybackDirection = PlaybackDirection.Alternate,
+ };
+
+ var rect = new Rectangle()
+ {
+ Width = 11,
+ };
+
+ var originalValue = rect.Width;
+
+ var clock = new TestClock();
+ var animationRun = animation.RunAsync(rect, clock);
+
+ clock.Step(TimeSpan.Zero);
+ Assert.Equal(rect.Width, 1);
+ clock.Step(TimeSpan.FromSeconds(2));
+ Assert.Equal(rect.Width, 2);
+ clock.Step(TimeSpan.FromSeconds(3));
+ //here we have invalid value so value should be expected and set to initial original value
+ Assert.Equal(rect.Width, originalValue);
+ }
+
[Fact]
public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present()
{
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
index 529b3b1aa8..22a9b28648 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs
@@ -47,7 +47,7 @@ namespace Avalonia.Controls.UnitTests
private class InvalidCollection : INotifyCollectionChanged, IEnumerable
{
- public event NotifyCollectionChangedEventHandler CollectionChanged;
+ public event NotifyCollectionChangedEventHandler CollectionChanged { add { } remove { } }
public IEnumerator GetEnumerator()
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
index fb3fd6d7d4..340eac0d4f 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
@@ -512,6 +512,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
+
+ [Fact]
+ public void Automatically_Converts_Color_To_SolidColorBrush_From_Setter()
+ {
+ using (StyledWindow())
+ {
+ var xaml = @"
+
+
+ #ff506070
+
+
+
+
+
+ ";
+
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var button = window.FindControl("button");
+ var brush = (ISolidColorBrush)button.Background;
+
+ Assert.Equal(0xff506070, brush.Color.ToUint32());
+ }
+ }
private IDisposable StyledWindow(params (string, string)[] assets)
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs
index 856b4615a5..e4f88706d8 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/TransformOperationsTests.cs
@@ -129,7 +129,7 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Single(operations);
Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type);
-
+
var expectedMatrix = new Matrix(1, 2, 3, 4, 5, 6);
Assert.Equal(expectedMatrix, operations[0].Matrix);
@@ -195,7 +195,7 @@ namespace Avalonia.Visuals.UnitTests.Media
[Theory]
[InlineData(0d, 10d)]
[InlineData(0.5d, 15d)]
- [InlineData(1d,20d)]
+ [InlineData(1d, 20d)]
public void Can_Interpolate_Rotation(double progress, double angle)
{
var from = TransformOperations.Parse("rotate(10deg)");
@@ -225,5 +225,73 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Single(operations);
Assert.Equal(TransformOperation.OperationType.Matrix, operations[0].Type);
}
+
+ [Fact]
+ public void Order_Of_Operations_Is_Preserved_No_Prefix()
+ {
+ var from = TransformOperations.Parse("scale(1)");
+ var to = TransformOperations.Parse("translate(50px,50px) scale(0.5,0.5)");
+
+ var interpolated_0 = TransformOperations.Interpolate(from, to, 0);
+
+ Assert.True(interpolated_0.IsIdentity);
+
+ var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5);
+
+ AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 12.5, translateY: 12.5);
+
+ var interpolated_100 = TransformOperations.Interpolate(from, to, 1);
+
+ AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 25, translateY: 25);
+ }
+
+ [Fact]
+ public void Order_Of_Operations_Is_Preserved_One_Prefix()
+ {
+ var from = TransformOperations.Parse("scale(1)");
+ var to = TransformOperations.Parse("scale(0.5,0.5) translate(50px,50px)");
+
+ var interpolated_0 = TransformOperations.Interpolate(from, to, 0);
+
+ Assert.True(interpolated_0.IsIdentity);
+
+ var interpolated_50 = TransformOperations.Interpolate(from, to, 0.5);
+
+ AssertMatrix(interpolated_50.Value, scaleX: 0.75, scaleY: 0.75, translateX: 25.0, translateY: 25);
+
+ var interpolated_100 = TransformOperations.Interpolate(from, to, 1);
+
+ AssertMatrix(interpolated_100.Value, scaleX: 0.5, scaleY: 0.5, translateX: 50, translateY: 50);
+ }
+
+ private static void AssertMatrix(Matrix matrix, double? angle = null, double? scaleX = null, double? scaleY = null, double? translateX = null, double? translateY = null)
+ {
+ Assert.True(Matrix.TryDecomposeTransform(matrix, out var composed));
+
+ if (angle.HasValue)
+ {
+ Assert.Equal(angle.Value, composed.Angle);
+ }
+
+ if (scaleX.HasValue)
+ {
+ Assert.Equal(scaleX.Value, composed.Scale.X);
+ }
+
+ if (scaleY.HasValue)
+ {
+ Assert.Equal(scaleY.Value, composed.Scale.Y);
+ }
+
+ if (translateX.HasValue)
+ {
+ Assert.Equal(translateX.Value, composed.Translate.X);
+ }
+
+ if (translateY.HasValue)
+ {
+ Assert.Equal(translateY.Value, composed.Translate.Y);
+ }
+ }
}
}