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 + + + + +