From c7bd2ba7b97a0a64ee3b3308302b37abf4506bf1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 29 Jul 2021 15:32:07 +0100 Subject: [PATCH] Merge pull request #6310 from AvaloniaUI/features/win32-allow-cancel-app-shutdown Features/win32 allow cancel app shutdown # Conflicts: # src/Avalonia.Controls/ApiCompatBaseline.txt --- src/Avalonia.Controls/ApiCompatBaseline.txt | 6 ++--- .../ClassicDesktopStyleApplicationLifetime.cs | 4 +-- ...IClassicDesktopStyleApplicationLifetime.cs | 17 ++++++++---- .../ShutdownRequestedEventArgs.cs | 9 +++++++ .../Platform/IPlatformLifetimeEventsImpl.cs | 3 ++- .../AvaloniaNativeApplicationPlatform.cs | 5 ++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 27 +++++++++++++++---- .../DesktopStyleApplicationLifetimeTests.cs | 2 +- 8 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 2c6e002026..ac6db5bdf7 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -1,9 +1,9 @@ Compat issues with assembly Avalonia.Controls: InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Action Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 79780dbd0b..2a42d99ac5 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.ApplicationLifetimes public event EventHandler Startup; /// - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; /// public event EventHandler Exit; @@ -134,7 +134,7 @@ namespace Avalonia.Controls.ApplicationLifetimes _activeLifetime = null; } - private void OnShutdownRequested(object sender, CancelEventArgs e) + private void OnShutdownRequested(object sender, ShutdownRequestedEventArgs e) { ShutdownRequested?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index ecf8a0358f..a70d5dd2f1 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -37,14 +37,21 @@ namespace Avalonia.Controls.ApplicationLifetimes IReadOnlyList Windows { get; } /// - /// Raised by the platform when a shutdown is requested. + /// Raised by the platform when an application shutdown is requested. /// /// - /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event - /// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application + /// Application Shutdown can be requested for various reasons like OS shutdown. + /// + /// On Windows this will be called when an OS Session (logout or shutdown) terminates. Cancelling the eventargs will + /// block OS shutdown. + /// + /// On OSX this has the same behavior as on Windows and in addition: + /// This event is raised via the Quit menu or right-clicking on the application icon and selecting Quit. + /// + /// This event provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application /// will try to close each non-owned open window, invoking the event on each and allowing - /// each window to cancel the shutdown. + /// each window to cancel the shutdown of the application. Windows cannot however prevent OS shutdown. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs new file mode 100644 index 0000000000..62bc3a8904 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ShutdownRequestedEventArgs.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ShutdownRequestedEventArgs : CancelEventArgs + { + + } +} diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index 8e660777e9..4cd6640453 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Platform { @@ -11,6 +12,6 @@ namespace Avalonia.Platform /// /// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. /// - event EventHandler ShutdownRequested; + event EventHandler ShutdownRequested; } } diff --git a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs index 77c0794d04..8084e06d28 100644 --- a/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Native.Interop; using Avalonia.Platform; @@ -7,7 +8,7 @@ namespace Avalonia.Native { internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl { - public event EventHandler ShutdownRequested; + public event EventHandler ShutdownRequested; void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) { @@ -17,7 +18,7 @@ namespace Avalonia.Native public int TryShutdown() { if (ShutdownRequested is null) return 1; - var e = new CancelEventArgs(); + var e = new ShutdownRequestedEventArgs(); ShutdownRequested(this, e); return (!e.Cancel).AsComBool(); } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index e3b01e8071..da449ae168 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -6,18 +6,16 @@ using System.IO; using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; -using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; -using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.Utilities; -using Avalonia.Win32; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -65,7 +63,7 @@ namespace Avalonia namespace Avalonia.Win32 { - class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader + class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -115,7 +113,8 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(new NonPumpingSyncContext.HelperImpl()) - .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()); + .Bind().ToConstant(new WindowsMountedVolumeInfoProvider()) + .Bind().ToConstant(s_instance); Win32GlManager.Initialize(); @@ -200,6 +199,8 @@ namespace Avalonia.Win32 public event Action Signaled; + public event EventHandler ShutdownRequested; + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { @@ -207,6 +208,22 @@ namespace Avalonia.Win32 { Signaled?.Invoke(null); } + + if(msg == (uint)WindowsMessage.WM_QUERYENDSESSION) + { + if (ShutdownRequested != null) + { + var e = new ShutdownRequestedEventArgs(); + + ShutdownRequested(this, e); + + if(e.Cancel) + { + return IntPtr.Zero; + } + } + } + return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs index 38713834c3..f7a3bdea1c 100644 --- a/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests ++raised; }; - lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs()); + lifetimeEvents.Raise(x => x.ShutdownRequested += null, new ShutdownRequestedEventArgs()); Assert.Equal(1, raised); Assert.Equal(new[] { window }, lifetime.Windows);