mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3367 from Jack251970/graceful_shutdown
Graceful shutdown
This commit is contained in:
commit
e5ee8567e6
5 changed files with 247 additions and 102 deletions
|
|
@ -27,11 +27,26 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class App : IDisposable, ISingleInstanceApp
|
public partial class App : IDisposable, ISingleInstanceApp
|
||||||
{
|
{
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
public static IPublicAPI API { get; private set; }
|
public static IPublicAPI API { get; private set; }
|
||||||
private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Fields
|
||||||
|
|
||||||
private static bool _disposed;
|
private static bool _disposed;
|
||||||
|
private MainWindow _mainWindow;
|
||||||
|
private readonly MainViewModel _mainVM;
|
||||||
private readonly Settings _settings;
|
private readonly Settings _settings;
|
||||||
|
|
||||||
|
// To prevent two disposals running at the same time.
|
||||||
|
private static readonly object _disposingLock = new();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructor
|
||||||
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
// Initialize settings
|
// Initialize settings
|
||||||
|
|
@ -79,27 +94,33 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
API = Ioc.Default.GetRequiredService<IPublicAPI>();
|
||||||
_settings.Initialize();
|
_settings.Initialize();
|
||||||
|
_mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
|
ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local function
|
||||||
|
static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
|
||||||
|
{
|
||||||
|
// Firstly show users the message
|
||||||
|
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
|
||||||
|
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
|
||||||
|
Environment.FailFast(message, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ShowErrorMsgBoxAndFailFast(string message, Exception e)
|
#endregion
|
||||||
{
|
|
||||||
// Firstly show users the message
|
|
||||||
MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
|
|
||||||
// Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info.
|
#region Main
|
||||||
Environment.FailFast(message, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main()
|
public static void Main()
|
||||||
{
|
{
|
||||||
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
|
if (SingleInstance<App>.InitializeAsFirstInstance())
|
||||||
{
|
{
|
||||||
using var application = new App();
|
using var application = new App();
|
||||||
application.InitializeComponent();
|
application.InitializeComponent();
|
||||||
|
|
@ -107,6 +128,10 @@ namespace Flow.Launcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region App Events
|
||||||
|
|
||||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||||
|
|
||||||
private async void OnStartup(object sender, StartupEventArgs e)
|
private async void OnStartup(object sender, StartupEventArgs e)
|
||||||
|
|
@ -142,11 +167,11 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
await imageLoadertask;
|
await imageLoadertask;
|
||||||
|
|
||||||
var window = new MainWindow();
|
_mainWindow = new MainWindow();
|
||||||
|
|
||||||
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
|
||||||
|
|
||||||
Current.MainWindow = window;
|
Current.MainWindow = _mainWindow;
|
||||||
Current.MainWindow.Title = Constant.FlowLauncher;
|
Current.MainWindow.Title = Constant.FlowLauncher;
|
||||||
|
|
||||||
HotKeyMapper.Initialize();
|
HotKeyMapper.Initialize();
|
||||||
|
|
@ -163,8 +188,7 @@ namespace Flow.Launcher
|
||||||
AutoUpdates();
|
AutoUpdates();
|
||||||
|
|
||||||
API.SaveAppAllSettings();
|
API.SaveAppAllSettings();
|
||||||
Log.Info(
|
Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------");
|
||||||
"|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- ");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +221,6 @@ namespace Flow.Launcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Conditional("RELEASE")]
|
|
||||||
private void AutoUpdates()
|
private void AutoUpdates()
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
|
|
@ -215,11 +238,29 @@ namespace Flow.Launcher
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Register Events
|
||||||
|
|
||||||
private void RegisterExitEvents()
|
private void RegisterExitEvents()
|
||||||
{
|
{
|
||||||
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
|
AppDomain.CurrentDomain.ProcessExit += (s, e) =>
|
||||||
Current.Exit += (s, e) => Dispose();
|
{
|
||||||
Current.SessionEnding += (s, e) => Dispose();
|
Log.Info("|App.RegisterExitEvents|Process Exit");
|
||||||
|
Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
Current.Exit += (s, e) =>
|
||||||
|
{
|
||||||
|
Log.Info("|App.RegisterExitEvents|Application Exit");
|
||||||
|
Dispose();
|
||||||
|
};
|
||||||
|
|
||||||
|
Current.SessionEnding += (s, e) =>
|
||||||
|
{
|
||||||
|
Log.Info("|App.RegisterExitEvents|Session Ending");
|
||||||
|
Dispose();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -240,20 +281,60 @@ namespace Flow.Launcher
|
||||||
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
// if sessionending is called, exit proverbially be called when log off / shutdown
|
// Prevent two disposes at the same time.
|
||||||
// but if sessionending is not called, exit won't be called when log off / shutdown
|
lock (_disposingLock)
|
||||||
if (!_disposed)
|
|
||||||
{
|
{
|
||||||
API.SaveAppAllSettings();
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stopwatch.Normal("|App.Dispose|Dispose cost", () =>
|
||||||
|
{
|
||||||
|
Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------");
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Dispose needs to be called on the main Windows thread,
|
||||||
|
// since some resources owned by the thread need to be disposed.
|
||||||
|
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
|
||||||
|
_mainVM?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ISingleInstanceApp
|
||||||
|
|
||||||
public void OnSecondAppStarted()
|
public void OnSecondAppStarted()
|
||||||
{
|
{
|
||||||
Ioc.Default.GetRequiredService<MainViewModel>().Show();
|
Ioc.Default.GetRequiredService<MainViewModel>().Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace Flow.Launcher.Helper
|
||||||
{
|
{
|
||||||
public interface ISingleInstanceApp
|
public interface ISingleInstanceApp
|
||||||
{
|
{
|
||||||
void OnSecondAppStarted();
|
void OnSecondAppStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -24,9 +24,7 @@ namespace Flow.Launcher.Helper
|
||||||
/// running as Administrator, can activate it with command line arguments.
|
/// running as Administrator, can activate it with command line arguments.
|
||||||
/// For most apps, this will not be much of an issue.
|
/// For most apps, this will not be much of an issue.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class SingleInstance<TApplication>
|
public static class SingleInstance<TApplication> where TApplication : Application, ISingleInstanceApp
|
||||||
where TApplication: Application , ISingleInstanceApp
|
|
||||||
|
|
||||||
{
|
{
|
||||||
#region Private Fields
|
#region Private Fields
|
||||||
|
|
||||||
|
|
@ -39,11 +37,12 @@ namespace Flow.Launcher.Helper
|
||||||
/// Suffix to the channel name.
|
/// Suffix to the channel name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
|
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
|
||||||
|
private const string InstanceMutexName = "Flow.Launcher_Unique_Application_Mutex";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Application mutex.
|
/// Application mutex.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static Mutex singleInstanceMutex;
|
internal static Mutex SingleInstanceMutex { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -54,24 +53,23 @@ namespace Flow.Launcher.Helper
|
||||||
/// If not, activates the first instance.
|
/// If not, activates the first instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if this is the first instance of the application.</returns>
|
/// <returns>True if this is the first instance of the application.</returns>
|
||||||
public static bool InitializeAsFirstInstance( string uniqueName )
|
public static bool InitializeAsFirstInstance()
|
||||||
{
|
{
|
||||||
// Build unique application Id and the IPC channel name.
|
// Build unique application Id and the IPC channel name.
|
||||||
string applicationIdentifier = uniqueName + Environment.UserName;
|
string applicationIdentifier = InstanceMutexName + Environment.UserName;
|
||||||
|
|
||||||
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
|
string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
|
||||||
|
|
||||||
// Create mutex based on unique application Id to check if this is the first instance of the application.
|
// Create mutex based on unique application Id to check if this is the first instance of the application.
|
||||||
bool firstInstance;
|
SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance);
|
||||||
singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
|
|
||||||
if (firstInstance)
|
if (firstInstance)
|
||||||
{
|
{
|
||||||
_ = CreateRemoteService(channelName);
|
_ = CreateRemoteServiceAsync(channelName);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_ = SignalFirstInstance(channelName);
|
_ = SignalFirstInstanceAsync(channelName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +79,7 @@ namespace Flow.Launcher.Helper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Cleanup()
|
public static void Cleanup()
|
||||||
{
|
{
|
||||||
singleInstanceMutex?.ReleaseMutex();
|
SingleInstanceMutex?.ReleaseMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -93,22 +91,19 @@ namespace Flow.Launcher.Helper
|
||||||
/// Once receives signal from client, will activate first instance.
|
/// Once receives signal from client, will activate first instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channelName">Application's IPC channel name.</param>
|
/// <param name="channelName">Application's IPC channel name.</param>
|
||||||
private static async Task CreateRemoteService(string channelName)
|
private static async Task CreateRemoteServiceAsync(string channelName)
|
||||||
{
|
{
|
||||||
using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In))
|
using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In);
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
while(true)
|
// Wait for connection to the pipe
|
||||||
{
|
await pipeServer.WaitForConnectionAsync();
|
||||||
// Wait for connection to the pipe
|
|
||||||
await pipeServer.WaitForConnectionAsync();
|
// Do an asynchronous call to ActivateFirstInstance function
|
||||||
if (Application.Current != null)
|
Application.Current?.Dispatcher.Invoke(ActivateFirstInstance);
|
||||||
{
|
|
||||||
// Do an asynchronous call to ActivateFirstInstance function
|
// Disconect client
|
||||||
Application.Current.Dispatcher.Invoke(ActivateFirstInstance);
|
pipeServer.Disconnect();
|
||||||
}
|
|
||||||
// Disconect client
|
|
||||||
pipeServer.Disconnect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,25 +114,13 @@ namespace Flow.Launcher.Helper
|
||||||
/// <param name="args">
|
/// <param name="args">
|
||||||
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
|
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
|
||||||
/// </param>
|
/// </param>
|
||||||
private static async Task SignalFirstInstance(string channelName)
|
private static async Task SignalFirstInstanceAsync(string channelName)
|
||||||
{
|
{
|
||||||
// Create a client pipe connected to server
|
// Create a client pipe connected to server
|
||||||
using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out))
|
using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out);
|
||||||
{
|
|
||||||
// Connect to the available pipe
|
|
||||||
await pipeClient.ConnectAsync(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
// Connect to the available pipe
|
||||||
/// Callback for activating first instance of the application.
|
await pipeClient.ConnectAsync(0);
|
||||||
/// </summary>
|
|
||||||
/// <param name="arg">Callback argument.</param>
|
|
||||||
/// <returns>Always null.</returns>
|
|
||||||
private static object ActivateFirstInstanceCallback(object o)
|
|
||||||
{
|
|
||||||
ActivateFirstInstance();
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
AllowDrop="True"
|
AllowDrop="True"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
|
Closed="OnClosed"
|
||||||
Closing="OnClosing"
|
Closing="OnClosing"
|
||||||
Deactivated="OnDeactivated"
|
Deactivated="OnDeactivated"
|
||||||
Icon="Images/app.png"
|
Icon="Images/app.png"
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ using Screen = System.Windows.Forms.Screen;
|
||||||
|
|
||||||
namespace Flow.Launcher
|
namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
public partial class MainWindow
|
public partial class MainWindow : IDisposable
|
||||||
{
|
{
|
||||||
#region Private Fields
|
#region Private Fields
|
||||||
|
|
||||||
|
|
@ -39,24 +39,30 @@ namespace Flow.Launcher
|
||||||
private NotifyIcon _notifyIcon;
|
private NotifyIcon _notifyIcon;
|
||||||
|
|
||||||
// Window Context Menu
|
// Window Context Menu
|
||||||
private readonly ContextMenu contextMenu = new();
|
private readonly ContextMenu _contextMenu = new();
|
||||||
private readonly MainViewModel _viewModel;
|
private readonly MainViewModel _viewModel;
|
||||||
|
|
||||||
// Window Event : Key Event
|
// Window Event: Close Event
|
||||||
private bool isArrowKeyPressed = false;
|
private bool _canClose = false;
|
||||||
|
// Window Event: Key Event
|
||||||
|
private bool _isArrowKeyPressed = false;
|
||||||
|
|
||||||
// Window Sound Effects
|
// Window Sound Effects
|
||||||
private MediaPlayer animationSoundWMP;
|
private MediaPlayer animationSoundWMP;
|
||||||
private SoundPlayer animationSoundWPF;
|
private SoundPlayer animationSoundWPF;
|
||||||
|
|
||||||
// Window WndProc
|
// Window WndProc
|
||||||
|
private HwndSource _hwndSource;
|
||||||
private int _initialWidth;
|
private int _initialWidth;
|
||||||
private int _initialHeight;
|
private int _initialHeight;
|
||||||
|
|
||||||
// Window Animation
|
// Window Animation
|
||||||
private const double DefaultRightMargin = 66; //* this value from base.xaml
|
private const double DefaultRightMargin = 66; //* this value from base.xaml
|
||||||
private bool _animating;
|
private bool _animating;
|
||||||
private bool _isClockPanelAnimating = false; // 애니메이션 실행 중인지 여부
|
private bool _isClockPanelAnimating = false;
|
||||||
|
|
||||||
|
// IDisposable
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -85,8 +91,8 @@ namespace Flow.Launcher
|
||||||
private void OnSourceInitialized(object sender, EventArgs e)
|
private void OnSourceInitialized(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var handle = Win32Helper.GetWindowHandle(this, true);
|
var handle = Win32Helper.GetWindowHandle(this, true);
|
||||||
var win = HwndSource.FromHwnd(handle);
|
_hwndSource = HwndSource.FromHwnd(handle);
|
||||||
win.AddHook(WndProc);
|
_hwndSource.AddHook(WndProc);
|
||||||
Win32Helper.HideFromAltTab(this);
|
Win32Helper.HideFromAltTab(this);
|
||||||
Win32Helper.DisableControlBox(this);
|
Win32Helper.DisableControlBox(this);
|
||||||
}
|
}
|
||||||
|
|
@ -218,15 +224,15 @@ namespace Flow.Launcher
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ QueryTextBox.Text 변경 감지 (글자 수 1 이상일 때만 동작하도록 수정)
|
// QueryTextBox.Text change detection (modified to only work when character count is 1 or higher)
|
||||||
QueryTextBox.TextChanged += (sender, e) => UpdateClockPanelVisibility();
|
QueryTextBox.TextChanged += (sender, e) => UpdateClockPanelVisibility();
|
||||||
|
|
||||||
// ✅ ContextMenu.Visibility 변경 감지
|
// Detecting ContextMenu.Visibility changes
|
||||||
DependencyPropertyDescriptor
|
DependencyPropertyDescriptor
|
||||||
.FromProperty(VisibilityProperty, typeof(ContextMenu))
|
.FromProperty(VisibilityProperty, typeof(ContextMenu))
|
||||||
.AddValueChanged(ContextMenu, (s, e) => UpdateClockPanelVisibility());
|
.AddValueChanged(ContextMenu, (s, e) => UpdateClockPanelVisibility());
|
||||||
|
|
||||||
// ✅ History.Visibility 변경 감지
|
// Detect History.Visibility changes
|
||||||
DependencyPropertyDescriptor
|
DependencyPropertyDescriptor
|
||||||
.FromProperty(VisibilityProperty, typeof(StackPanel)) // History는 StackPanel이라고 가정
|
.FromProperty(VisibilityProperty, typeof(StackPanel)) // History는 StackPanel이라고 가정
|
||||||
.AddValueChanged(History, (s, e) => UpdateClockPanelVisibility());
|
.AddValueChanged(History, (s, e) => UpdateClockPanelVisibility());
|
||||||
|
|
@ -234,18 +240,37 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private async void OnClosing(object sender, CancelEventArgs e)
|
private async void OnClosing(object sender, CancelEventArgs e)
|
||||||
{
|
{
|
||||||
_notifyIcon.Visible = false;
|
if (!_canClose)
|
||||||
App.API.SaveAppAllSettings();
|
{
|
||||||
e.Cancel = true;
|
_notifyIcon.Visible = false;
|
||||||
await PluginManager.DisposePluginsAsync();
|
App.API.SaveAppAllSettings();
|
||||||
Notification.Uninstall();
|
e.Cancel = true;
|
||||||
Environment.Exit(0);
|
await PluginManager.DisposePluginsAsync();
|
||||||
|
Notification.Uninstall();
|
||||||
|
// After plugins are all disposed, we can close the main window
|
||||||
|
_canClose = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosed(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_hwndSource.RemoveHook(WndProc);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
_hwndSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLocationChanged(object sender, EventArgs e)
|
private void OnLocationChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_animating)
|
if (_animating) return;
|
||||||
return;
|
|
||||||
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
|
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
|
||||||
{
|
{
|
||||||
_settings.WindowLeft = Left;
|
_settings.WindowLeft = Left;
|
||||||
|
|
@ -283,12 +308,12 @@ namespace Flow.Launcher
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.Down:
|
case Key.Down:
|
||||||
isArrowKeyPressed = true;
|
_isArrowKeyPressed = true;
|
||||||
_viewModel.SelectNextItemCommand.Execute(null);
|
_viewModel.SelectNextItemCommand.Execute(null);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
case Key.Up:
|
case Key.Up:
|
||||||
isArrowKeyPressed = true;
|
_isArrowKeyPressed = true;
|
||||||
_viewModel.SelectPrevItemCommand.Execute(null);
|
_viewModel.SelectPrevItemCommand.Execute(null);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -346,13 +371,13 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
if (e.Key == Key.Up || e.Key == Key.Down)
|
if (e.Key == Key.Up || e.Key == Key.Down)
|
||||||
{
|
{
|
||||||
isArrowKeyPressed = false;
|
_isArrowKeyPressed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
|
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (isArrowKeyPressed)
|
if (_isArrowKeyPressed)
|
||||||
{
|
{
|
||||||
e.Handled = true; // Ignore Mouse Hover when press Arrowkeys
|
e.Handled = true; // Ignore Mouse Hover when press Arrowkeys
|
||||||
}
|
}
|
||||||
|
|
@ -522,11 +547,11 @@ namespace Flow.Launcher
|
||||||
gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip");
|
gamemode.ToolTip = App.API.GetTranslation("GameModeToolTip");
|
||||||
positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip");
|
positionreset.ToolTip = App.API.GetTranslation("PositionResetToolTip");
|
||||||
|
|
||||||
contextMenu.Items.Add(open);
|
_contextMenu.Items.Add(open);
|
||||||
contextMenu.Items.Add(gamemode);
|
_contextMenu.Items.Add(gamemode);
|
||||||
contextMenu.Items.Add(positionreset);
|
_contextMenu.Items.Add(positionreset);
|
||||||
contextMenu.Items.Add(settings);
|
_contextMenu.Items.Add(settings);
|
||||||
contextMenu.Items.Add(exit);
|
_contextMenu.Items.Add(exit);
|
||||||
|
|
||||||
_notifyIcon.MouseClick += (o, e) =>
|
_notifyIcon.MouseClick += (o, e) =>
|
||||||
{
|
{
|
||||||
|
|
@ -537,14 +562,14 @@ namespace Flow.Launcher
|
||||||
break;
|
break;
|
||||||
case MouseButtons.Right:
|
case MouseButtons.Right:
|
||||||
|
|
||||||
contextMenu.IsOpen = true;
|
_contextMenu.IsOpen = true;
|
||||||
// Get context menu handle and bring it to the foreground
|
// Get context menu handle and bring it to the foreground
|
||||||
if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource)
|
if (PresentationSource.FromVisual(_contextMenu) is HwndSource hwndSource)
|
||||||
{
|
{
|
||||||
Win32Helper.SetForegroundWindow(hwndSource.Handle);
|
Win32Helper.SetForegroundWindow(hwndSource.Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenu.Focus();
|
_contextMenu.Focus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -552,7 +577,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
private void UpdateNotifyIconText()
|
private void UpdateNotifyIconText()
|
||||||
{
|
{
|
||||||
var menu = contextMenu;
|
var menu = _contextMenu;
|
||||||
((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") +
|
((MenuItem)menu.Items[0]).Header = App.API.GetTranslation("iconTrayOpen") +
|
||||||
" (" + _settings.Hotkey + ")";
|
" (" + _settings.Hotkey + ")";
|
||||||
((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode");
|
((MenuItem)menu.Items[1]).Header = App.API.GetTranslation("GameMode");
|
||||||
|
|
@ -748,7 +773,7 @@ namespace Flow.Launcher
|
||||||
if (_animating)
|
if (_animating)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
isArrowKeyPressed = true;
|
_isArrowKeyPressed = true;
|
||||||
_animating = true;
|
_animating = true;
|
||||||
UpdatePosition(false);
|
UpdatePosition(false);
|
||||||
|
|
||||||
|
|
@ -826,7 +851,7 @@ namespace Flow.Launcher
|
||||||
|
|
||||||
clocksb.Completed += (_, _) => _animating = false;
|
clocksb.Completed += (_, _) => _animating = false;
|
||||||
_settings.WindowLeft = Left;
|
_settings.WindowLeft = Left;
|
||||||
isArrowKeyPressed = false;
|
_isArrowKeyPressed = false;
|
||||||
|
|
||||||
if (QueryTextBox.Text.Length == 0)
|
if (QueryTextBox.Text.Length == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -995,5 +1020,30 @@ namespace Flow.Launcher
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_hwndSource?.Dispose();
|
||||||
|
_notifyIcon?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ using Microsoft.VisualStudio.Threading;
|
||||||
|
|
||||||
namespace Flow.Launcher.ViewModel
|
namespace Flow.Launcher.ViewModel
|
||||||
{
|
{
|
||||||
public partial class MainViewModel : BaseModel, ISavable
|
public partial class MainViewModel : BaseModel, ISavable, IDisposable
|
||||||
{
|
{
|
||||||
#region Private Fields
|
#region Private Fields
|
||||||
|
|
||||||
|
|
@ -1551,5 +1551,35 @@ namespace Flow.Launcher.ViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_updateSource?.Dispose();
|
||||||
|
_resultsUpdateChannelWriter?.Complete();
|
||||||
|
if (_resultsViewUpdateTask?.IsCompleted == true)
|
||||||
|
{
|
||||||
|
_resultsViewUpdateTask.Dispose();
|
||||||
|
}
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue