diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 53748f0206d..70c49814cb1 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -60,7 +60,6 @@ APeriod api APIENTRY APIIs -Apm APPBARDATA appdata APPICON @@ -130,7 +129,6 @@ Avanc Awaitable awakeness awakeversion -AWAYMODE AYUV backend backtracer @@ -498,7 +496,6 @@ dvr DVSD DVSL DVTARGETDEVICE -dwhkl DWINRT dwl dwm @@ -727,8 +724,6 @@ hhk HHmmss HHOOK hhx -Hiber -Hiberboot HIBYTE HICON HIDEWINDOW @@ -904,7 +899,6 @@ inheritdoc initguid Inkscape Inlines -Inlining inorder INotification INotify @@ -1034,7 +1028,6 @@ jxr jyuwono KBDLLHOOKSTRUCT kbm -KCode KEYBDINPUT keybindings keyboardeventhandlers @@ -1158,7 +1151,6 @@ lpsz lpt LPTHREAD LPTOP -lptpm LPTSTR LPVOID LPW @@ -1223,7 +1215,6 @@ Melman memcmp memcpy memset -MENUBREAK MENUITEMINFO MENUITEMINFOW Metadatas @@ -1428,7 +1419,6 @@ ntdll NTFS NTSTATUS nuget -nuint nullonfailure nullopt nullptr @@ -1490,7 +1480,6 @@ overlaywindow Overridable Oversampling OWNDC -OWNERDRAW PACL pagos PAINTSTRUCT @@ -1532,6 +1521,7 @@ pfo pft pgp pguid +PHANDLER phbm phbmp phwnd @@ -1934,6 +1924,7 @@ sse ssf ssh sstream +stackalloc STACKFRAME stackoverflow stackpanel @@ -2230,14 +2221,12 @@ VIDEOINFOHEADER viewbox viewmodel vih -Virt virtualization Virtualizing visiblecolorformats Visibletrue visualbrush visualstudio -viter VKey VKTAB vmovl diff --git a/src/common/Common.UI/CustomLibraryThemeProvider.cs b/src/common/Common.UI/CustomLibraryThemeProvider.cs index ddf3eb273b5..02799b65bff 100644 --- a/src/common/Common.UI/CustomLibraryThemeProvider.cs +++ b/src/common/Common.UI/CustomLibraryThemeProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; + using ControlzEx.Theming; namespace Common.UI diff --git a/src/modules/launcher/PowerLauncher/Helper/NativeEventWaiter.cs b/src/common/Common.UI/NativeEventWaiter.cs similarity index 68% rename from src/modules/launcher/PowerLauncher/Helper/NativeEventWaiter.cs rename to src/common/Common.UI/NativeEventWaiter.cs index 2cb658efba5..94aa459a1fa 100644 --- a/src/modules/launcher/PowerLauncher/Helper/NativeEventWaiter.cs +++ b/src/common/Common.UI/NativeEventWaiter.cs @@ -3,26 +3,24 @@ // See the LICENSE file in the project root for more information. using System; -using System.Reflection; using System.Threading; -using System.Windows; -using Wox.Plugin.Logger; -namespace PowerLauncher.Helper +using Dispatcher = System.Windows.Threading.Dispatcher; + +namespace Common.UI { public static class NativeEventWaiter { - public static void WaitForEventLoop(string eventName, Action callback, CancellationToken cancel) + public static void WaitForEventLoop(string eventName, Action callback, Dispatcher dispatcher, CancellationToken cancel) { new Thread(() => { - var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, eventName); while (true) { if (WaitHandle.WaitAny(new WaitHandle[] { cancel.WaitHandle, eventHandle }) == 1) { - Log.Info($"Successfully waited for {eventName}", MethodBase.GetCurrentMethod().DeclaringType); - Application.Current.Dispatcher.Invoke(callback); + dispatcher.BeginInvoke(callback); } else { diff --git a/src/modules/MeasureTool/MeasureToolUI/App.xaml.cs b/src/modules/MeasureTool/MeasureToolUI/App.xaml.cs index 348f1f9b940..c7afbc5065f 100644 --- a/src/modules/MeasureTool/MeasureToolUI/App.xaml.cs +++ b/src/modules/MeasureTool/MeasureToolUI/App.xaml.cs @@ -5,6 +5,7 @@ using System; using ManagedCommon; using MeasureToolUI.Helpers; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; namespace MeasureToolUI @@ -36,9 +37,10 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) { if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid)) { + var dispatcher = DispatcherQueue.GetForCurrentThread(); RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () => { - Environment.Exit(0); + dispatcher.TryEnqueue(App.Current.Exit); }); } } @@ -51,7 +53,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) catch (Exception ex) { Logger.LogError($"MeasureToolCore failed to initialize: {ex}"); - Environment.Exit(1); + App.Current.Exit(); + return; } _window = new MainWindow(core); diff --git a/src/modules/PowerOCR/PowerOCR/App.xaml.cs b/src/modules/PowerOCR/PowerOCR/App.xaml.cs index b8d96e4461f..ebfad3a5536 100644 --- a/src/modules/PowerOCR/PowerOCR/App.xaml.cs +++ b/src/modules/PowerOCR/PowerOCR/App.xaml.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; using System.Threading; using System.Windows; using ManagedCommon; @@ -23,6 +22,13 @@ public partial class App : Application, IDisposable private Mutex? _instanceMutex; private int _powerToysRunnerPid; + private CancellationTokenSource NativeThreadCTS { get; set; } + + public App() + { + NativeThreadCTS = new CancellationTokenSource(); + } + public void Dispose() { GC.SuppressFinalize(this); @@ -37,7 +43,7 @@ private void Application_Startup(object sender, StartupEventArgs e) { Logger.LogWarning("Another running TextExtractor instance was detected. Exiting TextExtractor"); _instanceMutex = null; - Environment.Exit(0); + Shutdown(); return; } @@ -51,10 +57,11 @@ private void Application_Startup(object sender, StartupEventArgs e) RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () => { Logger.LogInfo("PowerToys Runner exited. Exiting TextExtractor"); - Environment.Exit(0); + NativeThreadCTS.Cancel(); + Application.Current.Dispatcher.Invoke(() => Shutdown()); }); var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker()); - eventMonitor = new EventMonitor(); + eventMonitor = new EventMonitor(Application.Current.Dispatcher, NativeThreadCTS.Token); } catch (Exception ex) { diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/NativeEventWaiter.cs b/src/modules/PowerOCR/PowerOCR/Helpers/NativeEventWaiter.cs deleted file mode 100644 index 64d88dcd9bb..00000000000 --- a/src/modules/PowerOCR/PowerOCR/Helpers/NativeEventWaiter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Windows; - -namespace PowerOCR.Helpers -{ - public static class NativeEventWaiter - { - public static void WaitForEventLoop(string eventName, Action callback) - { - new Thread(() => - { - var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); - while (true) - { - if (eventHandle.WaitOne()) - { - Logger.LogInfo($"Successfully waited for {eventName}"); - Application.Current.Dispatcher.Invoke(callback); - } - } - }).Start(); - } - } -} diff --git a/src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs b/src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs index a94bec6aa75..790849e6fb5 100644 --- a/src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs +++ b/src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs @@ -4,6 +4,7 @@ using System; using System.Windows.Interop; +using Common.UI; using interop; using PowerOCR.Helpers; using PowerOCR.Utilities; @@ -16,9 +17,9 @@ namespace PowerOCR.Keyboard /// internal class EventMonitor { - public EventMonitor() + public EventMonitor(System.Windows.Threading.Dispatcher dispatcher, System.Threading.CancellationToken exitToken) { - NativeEventWaiter.WaitForEventLoop(Constants.ShowPowerOCRSharedEvent(), StartOCRSession); + NativeEventWaiter.WaitForEventLoop(Constants.ShowPowerOCRSharedEvent(), StartOCRSession, dispatcher, exitToken); } public void StartOCRSession() diff --git a/src/modules/awake/Awake/Awake.csproj b/src/modules/awake/Awake/Awake.csproj index e20d16db283..5ed03583429 100644 --- a/src/modules/awake/Awake/Awake.csproj +++ b/src/modules/awake/Awake/Awake.csproj @@ -43,6 +43,9 @@ + + all + diff --git a/src/modules/awake/Awake/Core/APIHelper.cs b/src/modules/awake/Awake/Core/APIHelper.cs index ad27f03a376..6b6222dfb34 100644 --- a/src/modules/awake/Awake/Core/APIHelper.cs +++ b/src/modules/awake/Awake/Core/APIHelper.cs @@ -7,17 +7,20 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; using Awake.Core.Models; using Microsoft.Win32; using NLog; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Storage.FileSystem; +using Windows.Win32.System.Console; +using Windows.Win32.System.Power; namespace Awake.Core { - public delegate bool ConsoleEventHandler(ControlType ctrlType); - /// /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts /// of the codebase. @@ -25,9 +28,6 @@ namespace Awake.Core public class APIHelper { private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion"; - private const int StdOutputHandle = -11; - private const uint GenericWrite = 0x40000000; - private const uint GenericRead = 0x80000000; private static readonly Logger _log; private static CancellationTokenSource _tokenSource; @@ -43,21 +43,21 @@ static APIHelper() _tokenSource = new CancellationTokenSource(); } - public static void SetConsoleControlHandler(ConsoleEventHandler handler, bool addHandler) + internal static void SetConsoleControlHandler(PHANDLER_ROUTINE handler, bool addHandler) { - NativeMethods.SetConsoleCtrlHandler(handler, addHandler); + PInvoke.SetConsoleCtrlHandler(handler, addHandler); } public static void AllocateConsole() { _log.Debug("Bootstrapping the console allocation routine."); - NativeMethods.AllocConsole(); + PInvoke.AllocConsole(); _log.Debug($"Console allocation result: {Marshal.GetLastWin32Error()}"); - var outputFilePointer = NativeMethods.CreateFile("CONOUT$", GenericRead | GenericWrite, FileShare.Write, IntPtr.Zero, FileMode.OpenOrCreate, 0, IntPtr.Zero); + var outputFilePointer = PInvoke.CreateFile("CONOUT$", FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE, FILE_SHARE_MODE.FILE_SHARE_WRITE, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, 0, null); _log.Debug($"CONOUT creation result: {Marshal.GetLastWin32Error()}"); - NativeMethods.SetStdHandle(StdOutputHandle, outputFilePointer); + PInvoke.SetStdHandle(Windows.Win32.System.Console.STD_HANDLE.STD_OUTPUT_HANDLE, outputFilePointer); _log.Debug($"SetStdHandle result: {Marshal.GetLastWin32Error()}"); Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true }); @@ -70,11 +70,11 @@ public static void AllocateConsole() /// /// Single or multiple EXECUTION_STATE entries. /// true if successful, false if failed - private static bool SetAwakeState(ExecutionState state) + private static bool SetAwakeState(EXECUTION_STATE state) { try { - var stateResult = NativeMethods.SetThreadExecutionState(state); + var stateResult = PInvoke.SetThreadExecutionState(state); return stateResult != 0; } catch @@ -160,18 +160,18 @@ private static bool RunIndefiniteLoop(bool keepDisplayOn = false) bool success; if (keepDisplayOn) { - success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS); + success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS); } else { - success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS); + success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS); } try { if (success) { - _log.Info($"Initiated indefinite keep awake in background thread: {NativeMethods.GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); + _log.Info($"Initiated indefinite keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); WaitHandle.WaitAny(new[] { _threadToken.WaitHandle }); @@ -186,28 +186,35 @@ private static bool RunIndefiniteLoop(bool keepDisplayOn = false) catch (OperationCanceledException ex) { // Task was clearly cancelled. - _log.Info($"Background thread termination: {NativeMethods.GetCurrentThreadId()}. Message: {ex.Message}"); + _log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}"); return success; } } - internal static void CompleteExit(int exitCode, bool force = false) + internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false) { - APIHelper.SetNoKeepAwake(); - TrayHelper.ClearTray(); + SetNoKeepAwake(); - // Because we are running a message loop for the tray, we can't just use Environment.Exit, - // but have to make sure that we properly send the termination message. - IntPtr windowHandle = APIHelper.GetHiddenWindow(); + HWND windowHandle = GetHiddenWindow(); - if (windowHandle != IntPtr.Zero) + if (windowHandle != HWND.Null) { - NativeMethods.SendMessage(windowHandle, NativeConstants.WM_CLOSE, 0, string.Empty); + PInvoke.SendMessage(windowHandle, PInvoke.WM_CLOSE, 0, 0); } if (force) { - Environment.Exit(exitCode); + PInvoke.PostQuitMessage(0); + } + + try + { + exitSignal?.Set(); + PInvoke.DestroyWindow(windowHandle); + } + catch (Exception ex) + { + _log.Info($"Exit signal error ${ex}"); } } @@ -221,18 +228,18 @@ private static bool RunTimedLoop(uint seconds, bool keepDisplayOn = true) { if (keepDisplayOn) { - success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS); + success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS); } else { - success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS); + success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS); } if (success) { - _log.Info($"Initiated temporary keep awake in background thread: {NativeMethods.GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); + _log.Info($"Initiated temporary keep awake in background thread: {PInvoke.GetCurrentThreadId()}. Screen on: {keepDisplayOn}"); - _timedLoopTimer = new System.Timers.Timer(seconds * 1000); + _timedLoopTimer = new System.Timers.Timer((seconds * 1000) + 1); _timedLoopTimer.Elapsed += (s, e) => { _tokenSource.Cancel(); @@ -262,7 +269,7 @@ private static bool RunTimedLoop(uint seconds, bool keepDisplayOn = true) catch (OperationCanceledException ex) { // Task was clearly cancelled. - _log.Info($"Background thread termination: {NativeMethods.GetCurrentThreadId()}. Message: {ex.Message}"); + _log.Info($"Background thread termination: {PInvoke.GetCurrentThreadId()}. Message: {ex.Message}"); return success; } } @@ -294,15 +301,20 @@ public static string GetOperatingSystemBuild() } [SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")] - public static IEnumerable EnumerateWindowsForProcess(int processId) + internal static IEnumerable EnumerateWindowsForProcess(int processId) { - var handles = new List(); - IntPtr hCurrentWnd = IntPtr.Zero; + var handles = new List(); + var hCurrentWnd = HWND.Null; do { - hCurrentWnd = NativeMethods.FindWindowEx(IntPtr.Zero, hCurrentWnd, null, null); - NativeMethods.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId); + hCurrentWnd = PInvoke.FindWindowEx(HWND.Null, hCurrentWnd, null as string, null); + uint targetProcessId = 0; + unsafe + { + PInvoke.GetWindowThreadProcessId(hCurrentWnd, &targetProcessId); + } + if (targetProcessId == processId) { handles.Add(hCurrentWnd); @@ -314,23 +326,30 @@ public static IEnumerable EnumerateWindowsForProcess(int processId) } [SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")] - public static IntPtr GetHiddenWindow() + internal static HWND GetHiddenWindow() { - IEnumerable windowHandles = EnumerateWindowsForProcess(Environment.ProcessId); + IEnumerable windowHandles = EnumerateWindowsForProcess(Environment.ProcessId); var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x"); string targetClass = $"{InternalConstants.TrayWindowId}{domain}"; - foreach (var handle in windowHandles) + unsafe { - StringBuilder className = new (256); - int classQueryResult = NativeMethods.GetClassName(handle, className, className.Capacity); - if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase)) + var classNameLen = 256; + Span className = stackalloc char[classNameLen]; + foreach (var handle in windowHandles) { - return handle; + fixed (char* ptr = className) + { + int classQueryResult = PInvoke.GetClassName(handle, ptr, classNameLen); + if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase)) + { + return handle; + } + } } } - return IntPtr.Zero; + return HWND.Null; } public static Dictionary GetDefaultTrayOptions() diff --git a/src/modules/awake/Awake/Core/Models/ExecutionState.cs b/src/modules/awake/Awake/Core/Models/ExecutionState.cs deleted file mode 100644 index 3c5ab849f14..00000000000 --- a/src/modules/awake/Awake/Core/Models/ExecutionState.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace Awake.Core.Models -{ - [Flags] - public enum ExecutionState : uint - { - ES_AWAYMODE_REQUIRED = 0x00000040, - ES_CONTINUOUS = 0x80000000, - ES_DISPLAY_REQUIRED = 0x00000002, - ES_SYSTEM_REQUIRED = 0x00000001, - } -} diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs b/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs deleted file mode 100644 index c8039411308..00000000000 --- a/src/modules/awake/Awake/Core/Models/SystemPowerCapabilities.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.InteropServices; - -namespace Awake.Core.Models -{ - public struct SystemPowerCapabilities - { - [MarshalAs(UnmanagedType.U1)] - public bool PowerButtonPresent; - [MarshalAs(UnmanagedType.U1)] - public bool SleepButtonPresent; - [MarshalAs(UnmanagedType.U1)] - public bool LidPresent; - [MarshalAs(UnmanagedType.U1)] - public bool SystemS1; - [MarshalAs(UnmanagedType.U1)] - public bool SystemS2; - [MarshalAs(UnmanagedType.U1)] - public bool SystemS3; - [MarshalAs(UnmanagedType.U1)] - public bool SystemS4; - [MarshalAs(UnmanagedType.U1)] - public bool SystemS5; - [MarshalAs(UnmanagedType.U1)] - public bool HiberFilePresent; - [MarshalAs(UnmanagedType.U1)] - public bool FullWake; - [MarshalAs(UnmanagedType.U1)] - public bool VideoDimPresent; - [MarshalAs(UnmanagedType.U1)] - public bool ApmPresent; - [MarshalAs(UnmanagedType.U1)] - public bool UpsPresent; - [MarshalAs(UnmanagedType.U1)] - public bool ThermalControl; - [MarshalAs(UnmanagedType.U1)] - public bool ProcessorThrottle; - public byte ProcessorMinThrottle; - public byte ProcessorMaxThrottle; - [MarshalAs(UnmanagedType.U1)] - public bool FastSystemS4; - [MarshalAs(UnmanagedType.U1)] - public bool Hiberboot; - [MarshalAs(UnmanagedType.U1)] - public bool WakeAlarmPresent; - [MarshalAs(UnmanagedType.U1)] - public bool AoAc; - [MarshalAs(UnmanagedType.U1)] - public bool DiskSpinDown; - public byte HiberFileType; - [MarshalAs(UnmanagedType.U1)] - public bool AoAcConnectivitySupported; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - private readonly byte[] spare3; - [MarshalAs(UnmanagedType.U1)] - public bool SystemBatteriesPresent; - [MarshalAs(UnmanagedType.U1)] - public bool BatteriesAreShortTerm; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public BatteryReportingScale[] BatteryScale; - public SystemPowerState AcOnLineWake; - public SystemPowerState SoftLidWake; - public SystemPowerState RtcWake; - public SystemPowerState MinDeviceWakeState; - public SystemPowerState DefaultLowLatencyWake; - } -} diff --git a/src/modules/awake/Awake/Core/Models/SystemPowerState.cs b/src/modules/awake/Awake/Core/Models/SystemPowerState.cs deleted file mode 100644 index 337333612fb..00000000000 --- a/src/modules/awake/Awake/Core/Models/SystemPowerState.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Awake.Core.Models -{ - // Maps to the OS power state. - // See documentation: https://docs.microsoft.com/windows/win32/power/system-power-states - public enum SystemPowerState - { - PowerSystemUnspecified = 0, - PowerSystemWorking = 1, - PowerSystemSleeping1 = 2, - PowerSystemSleeping2 = 3, - PowerSystemSleeping3 = 4, - PowerSystemHibernate = 5, - PowerSystemShutdown = 6, - PowerSystemMaximum = 7, - } -} diff --git a/src/modules/awake/Awake/Core/Models/TrayCommands.cs b/src/modules/awake/Awake/Core/Models/TrayCommands.cs index e93e8acf732..cca274fd5f6 100644 --- a/src/modules/awake/Awake/Core/Models/TrayCommands.cs +++ b/src/modules/awake/Awake/Core/Models/TrayCommands.cs @@ -2,14 +2,16 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Windows.Win32; + namespace Awake.Core.Models { internal enum TrayCommands : uint { - TC_DISPLAY_SETTING = NativeConstants.WM_USER + 1, - TC_MODE_PASSIVE = NativeConstants.WM_USER + 2, - TC_MODE_INDEFINITE = NativeConstants.WM_USER + 3, - TC_EXIT = NativeConstants.WM_USER + 4, - TC_TIME = NativeConstants.WM_USER + 5, + TC_DISPLAY_SETTING = PInvoke.WM_USER + 1, + TC_MODE_PASSIVE = PInvoke.WM_USER + 2, + TC_MODE_INDEFINITE = PInvoke.WM_USER + 3, + TC_EXIT = PInvoke.WM_USER + 4, + TC_TIME = PInvoke.WM_USER + 5, } } diff --git a/src/modules/awake/Awake/Core/NativeConstants.cs b/src/modules/awake/Awake/Core/NativeConstants.cs deleted file mode 100644 index 0aebf3e3b59..00000000000 --- a/src/modules/awake/Awake/Core/NativeConstants.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#pragma warning disable SA1310 // Field names should not contain underscore - -namespace Awake.Core -{ - internal class NativeConstants - { - internal const uint WM_COMMAND = 0x111; - internal const uint WM_USER = 0x400; - internal const uint WM_GETTEXT = 0x000D; - internal const uint WM_CLOSE = 0x0010; - - // Popup menu constants. - internal const uint MF_BYPOSITION = 1024; - internal const uint MF_STRING = 0; - internal const uint MF_MENUBREAK = 0x00000040; - internal const uint MF_SEPARATOR = 0x00000800; - internal const uint MF_POPUP = 0x00000010; - internal const uint MF_UNCHECKED = 0x00000000; - internal const uint MF_CHECKED = 0x00000008; - internal const uint MF_OWNERDRAW = 0x00000100; - } -} diff --git a/src/modules/awake/Awake/Core/NativeMethods.cs b/src/modules/awake/Awake/Core/NativeMethods.cs deleted file mode 100644 index 067a35dee7b..00000000000 --- a/src/modules/awake/Awake/Core/NativeMethods.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Awake.Core.Models; - -namespace Awake.Core -{ - internal static class NativeMethods - { - internal delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam); - - [DllImport("Powrprof.dll", SetLastError = true)] - internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler handler, bool add); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] - internal static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool AllocConsole(); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle); - - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern uint GetCurrentThreadId(); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] - internal static extern IntPtr CreateFile( - [MarshalAs(UnmanagedType.LPWStr)] string filename, - [MarshalAs(UnmanagedType.U4)] uint access, - [MarshalAs(UnmanagedType.U4)] FileShare share, - IntPtr securityAttributes, - [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, - [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, - IntPtr templateFile); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern IntPtr CreatePopupMenu(); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern bool InsertMenu(IntPtr hMenu, uint uPosition, uint uFlags, uint uIDNewItem, string lpNewItem); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr hWndChildAfter, string? className, string? windowTitle); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool SetForegroundWindow(IntPtr hWnd); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, nuint wParam, string lParam); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern bool DestroyMenu(IntPtr hMenu); - } -} diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 8e132dc1e74..5b1aca7f5c5 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -7,11 +7,16 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Awake.Core.Models; using Microsoft.PowerToys.Settings.UI.Library; using NLog; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; #pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8603 // Possible null reference return. @@ -22,21 +27,18 @@ internal static class TrayHelper { private static readonly Logger _log; - private static IntPtr _trayMenu; + private static DestroyMenuSafeHandle TrayMenu { get; set; } - private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; } - - private static NotifyIcon? _trayIcon; - - private static NotifyIcon TrayIcon { get => _trayIcon; set => _trayIcon = value; } + private static NotifyIcon TrayIcon { get; set; } static TrayHelper() { _log = LogManager.GetCurrentClassLogger(); + TrayMenu = new DestroyMenuSafeHandle(); TrayIcon = new NotifyIcon(); } - public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null) + public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal, ContextMenuStrip? contextMenu = null) { Task.Factory.StartNew( (tray) => @@ -49,7 +51,7 @@ public static void InitializeTray(string text, Icon icon, ContextMenuStrip? cont ((NotifyIcon?)tray).ContextMenuStrip = contextMenu; ((NotifyIcon?)tray).Visible = true; ((NotifyIcon?)tray).MouseClick += TrayClickHandler; - Application.AddMessageFilter(new TrayMessageFilter()); + Application.AddMessageFilter(new TrayMessageFilter(exitSignal)); Application.Run(); _log.Info("Tray setup complete."); } @@ -74,21 +76,15 @@ public static void InitializeTray(string text, Icon icon, ContextMenuStrip? cont /// MouseEventArgs instance containing mouse click event information. private static void TrayClickHandler(object? sender, MouseEventArgs e) { - IntPtr windowHandle = APIHelper.GetHiddenWindow(); + HWND windowHandle = APIHelper.GetHiddenWindow(); - if (windowHandle != IntPtr.Zero) + if (windowHandle != HWND.Null) { - NativeMethods.SetForegroundWindow(windowHandle); - NativeMethods.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, IntPtr.Zero); + PInvoke.SetForegroundWindow(windowHandle); + PInvoke.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, null); } } - public static void ClearTray() - { - TrayIcon.Icon = null; - TrayIcon.Dispose(); - } - internal static void SetTray(string text, AwakeSettings settings) { SetTray( @@ -101,20 +97,15 @@ internal static void SetTray(string text, AwakeSettings settings) [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:Single line comments should begin with single space", Justification = "For debugging purposes - will remove later.")] public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary trayTimeShortcuts) { - if (TrayMenu != IntPtr.Zero) + TrayMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu()); + + if (!TrayMenu.IsInvalid) { - var destructionStatus = NativeMethods.DestroyMenu(TrayMenu); - if (destructionStatus != true) - { - _log.Error("Failed to destroy tray menu and free up memory."); - } + PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit"); + PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_SEPARATOR, 0, string.Empty); + PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (keepDisplayOn ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on"); } - TrayMenu = NativeMethods.CreatePopupMenu(); - NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit"); - NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_SEPARATOR, 0, string.Empty); - NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (keepDisplayOn ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on"); - // In case there are no tray shortcuts defined for the application default to a // reasonable initial set. if (trayTimeShortcuts.Count == 0) @@ -123,18 +114,18 @@ public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dict } // TODO: Make sure that this loads from JSON instead of being hard-coded. - var awakeTimeMenu = NativeMethods.CreatePopupMenu(); + var awakeTimeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false); for (int i = 0; i < trayTimeShortcuts.Count; i++) { - NativeMethods.InsertMenu(awakeTimeMenu, (uint)i, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key); + PInvoke.InsertMenu(awakeTimeMenu, (uint)i, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key); } - var modeMenu = NativeMethods.CreatePopupMenu(); - NativeMethods.InsertMenu(modeMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (mode == AwakeMode.PASSIVE ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)"); - NativeMethods.InsertMenu(modeMenu, 1, NativeConstants.MF_BYPOSITION | NativeConstants.MF_STRING | (mode == AwakeMode.INDEFINITE ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely"); + var modeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false); + PInvoke.InsertMenu(modeMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.PASSIVE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)"); + PInvoke.InsertMenu(modeMenu, 1, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING | (mode == AwakeMode.INDEFINITE ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely"); - NativeMethods.InsertMenu(modeMenu, 2, NativeConstants.MF_BYPOSITION | NativeConstants.MF_POPUP | (mode == AwakeMode.TIMED ? NativeConstants.MF_CHECKED : NativeConstants.MF_UNCHECKED), (uint)awakeTimeMenu, "Keep awake temporarily"); - NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_POPUP, (uint)modeMenu, "Mode"); + PInvoke.InsertMenu(modeMenu, 2, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP | (mode == AwakeMode.TIMED ? MENU_ITEM_FLAGS.MF_CHECKED : MENU_ITEM_FLAGS.MF_UNCHECKED), (uint)awakeTimeMenu.DangerousGetHandle(), "Keep awake temporarily"); + PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP, (uint)modeMenu.DangerousGetHandle(), "Mode"); TrayIcon.Text = text; } diff --git a/src/modules/awake/Awake/Core/TrayMessageFilter.cs b/src/modules/awake/Awake/Core/TrayMessageFilter.cs index 00a156fb042..06e07e10312 100644 --- a/src/modules/awake/Awake/Core/TrayMessageFilter.cs +++ b/src/modules/awake/Awake/Core/TrayMessageFilter.cs @@ -6,9 +6,11 @@ using System.IO; using System.Linq; using System.Text.Json; +using System.Threading; using System.Windows.Forms; using Awake.Core.Models; using Microsoft.PowerToys.Settings.UI.Library; +using Windows.Win32; #pragma warning disable CS8603 // Possible null reference return. @@ -20,8 +22,11 @@ public class TrayMessageFilter : IMessageFilter private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; } - public TrayMessageFilter() + private static ManualResetEvent? _exitSignal; + + public TrayMessageFilter(ManualResetEvent? exitSignal) { + _exitSignal = exitSignal; ModuleSettings = new SettingsUtils(); } @@ -31,12 +36,12 @@ public bool PreFilterMessage(ref Message m) switch (m.Msg) { - case (int)NativeConstants.WM_COMMAND: + case (int)PInvoke.WM_COMMAND: var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF; switch (targetCommandIndex) { case (long)TrayCommands.TC_EXIT: - ExitCommandHandler(); + ExitCommandHandler(_exitSignal); break; case (long)TrayCommands.TC_DISPLAY_SETTING: DisplaySettingCommandHandler(InternalConstants.AppName); @@ -68,9 +73,9 @@ public bool PreFilterMessage(ref Message m) return false; } - private static void ExitCommandHandler() + private static void ExitCommandHandler(ManualResetEvent? exitSignal) { - APIHelper.CompleteExit(0, true); + APIHelper.CompleteExit(0, exitSignal, true); } private static void DisplaySettingCommandHandler(string moduleName) diff --git a/src/modules/awake/Awake/NativeMethods.txt b/src/modules/awake/Awake/NativeMethods.txt new file mode 100644 index 00000000000..a39796d7b5b --- /dev/null +++ b/src/modules/awake/Awake/NativeMethods.txt @@ -0,0 +1,23 @@ +AllocConsole +CreateFile +CreatePopupMenu +DestroyMenu +DestroyWindow +FindWindowEx +GetClassName +GetCurrentThreadId +GetPwrCapabilities +GetWindowThreadProcessId +HMENU +InsertMenu +PostQuitMessage +SendMessage +SetConsoleCtrlHandler +SetForegroundWindow +SetStdHandle +SetThreadExecutionState +TrackPopupMenuEx +WM_CLOSE +WM_COMMAND +WM_GETTEXT +WM_USER \ No newline at end of file diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index ff488343ce8..bf9a4502807 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.Diagnostics; @@ -17,11 +16,15 @@ using System.Threading; using System.Threading.Tasks; using Awake.Core; -using Awake.Core.Models; using interop; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Telemetry.Events; using NLog; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Console; +using Windows.Win32.System.Power; #pragma warning disable CS8602 // Dereference of a possibly null reference. #pragma warning disable CS8603 // Possible null reference return. @@ -47,8 +50,8 @@ internal class Program private static Logger? _log; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - private static ConsoleEventHandler _handler; - private static SystemPowerCapabilities _powerCapabilities; + private static PHANDLER_ROUTINE _handler; + private static SYSTEM_POWER_CAPABILITIES _powerCapabilities; #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. private static ManualResetEvent _exitSignal = new ManualResetEvent(false); @@ -63,7 +66,7 @@ private static int Main(string[] args) if (!instantiated) { - Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, true); + Exit(InternalConstants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true); } _settingsUtils = new SettingsUtils(); @@ -82,12 +85,12 @@ private static int Main(string[] args) // To make it easier to diagnose future issues, let's get the // system power capabilities and aggregate them in the log. - NativeMethods.GetPwrCapabilities(out _powerCapabilities); + PInvoke.GetPwrCapabilities(out _powerCapabilities); _log.Info(JsonSerializer.Serialize(_powerCapabilities)); _log.Info("Parsing parameters..."); - Option? configOption = new ( + var configOption = new Option( aliases: new[] { "--use-pt-config", "-c" }, getDefaultValue: () => false, description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.") @@ -100,7 +103,7 @@ private static int Main(string[] args) configOption.Required = false; - Option? displayOption = new ( + var displayOption = new Option( aliases: new[] { "--display-on", "-d" }, getDefaultValue: () => true, description: "Determines whether the display should be kept awake.") @@ -113,7 +116,7 @@ private static int Main(string[] args) displayOption.Required = false; - Option? timeOption = new ( + var timeOption = new Option( aliases: new[] { "--time-limit", "-t" }, getDefaultValue: () => 0, description: "Determines the interval, in seconds, during which the computer is kept awake.") @@ -126,7 +129,7 @@ private static int Main(string[] args) timeOption.Required = false; - Option? pidOption = new ( + var pidOption = new Option( aliases: new[] { "--pid", "-p" }, getDefaultValue: () => 0, description: $"Bind the execution of {InternalConstants.AppName} to another process.") @@ -156,23 +159,23 @@ private static int Main(string[] args) return rootCommand.InvokeAsync(args).Result; } - private static bool ExitHandler(ControlType ctrlType) + private static BOOL ExitHandler(uint ctrlType) { _log.Info($"Exited through handler with control type: {ctrlType}"); - Exit("Exiting from the internal termination handler.", Environment.ExitCode); + Exit("Exiting from the internal termination handler.", Environment.ExitCode, _exitSignal); return false; } - private static void Exit(string message, int exitCode, bool force = false) + private static void Exit(string message, int exitCode, ManualResetEvent exitSignal, bool force = false) { _log.Info(message); - APIHelper.CompleteExit(exitCode, force); + APIHelper.CompleteExit(exitCode, exitSignal, force); } private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid) { - _handler += new ConsoleEventHandler(ExitHandler); + _handler += ExitHandler; APIHelper.SetConsoleControlHandler(_handler, true); if (pid == 0) @@ -192,16 +195,15 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, // and instead watch for changes in the file. try { + var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, Constants.AwakeExitEvent()); new Thread(() => { - EventWaitHandle? eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AwakeExitEvent()); - if (eventHandle.WaitOne()) + if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1) { - Exit("Received a signal to end the process. Making sure we quit...", 0, true); + Exit("Received a signal to end the process. Making sure we quit...", 0, _exitSignal, true); } }).Start(); - - TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico")); + TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"), _exitSignal); string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName); _log.Info($"Reading configuration file: {settingsPath}"); @@ -263,7 +265,7 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, RunnerHelper.WaitForPowerToysRunner(pid, () => { _log.Info($"Triggered PID-based exit handler for PID {pid}."); - Exit("Terminating from process binding hook.", 0, true); + Exit("Terminating from process binding hook.", 0, _exitSignal, true); }); } diff --git a/src/modules/colorPicker/ColorPickerUI/App.xaml.cs b/src/modules/colorPicker/ColorPickerUI/App.xaml.cs index 698d530122e..d5f020272bc 100644 --- a/src/modules/colorPicker/ColorPickerUI/App.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/App.xaml.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.ComponentModel.Composition; using System.Threading; using System.Windows; using ColorPicker.Helpers; @@ -23,8 +24,16 @@ public partial class App : Application, IDisposable private bool disposedValue; private ThemeManager _themeManager; + private CancellationTokenSource NativeThreadCTS { get; set; } + + [Export] + private static CancellationToken ExitToken { get; set; } + protected override void OnStartup(StartupEventArgs e) { + NativeThreadCTS = new CancellationTokenSource(); + ExitToken = NativeThreadCTS.Token; + _args = e?.Args; // allow only one instance of color picker @@ -33,7 +42,7 @@ protected override void OnStartup(StartupEventArgs e) { Logger.LogWarning("There is ColorPicker instance running. Exiting Color Picker"); _instanceMutex = null; - Environment.Exit(0); + Shutdown(0); return; } @@ -45,7 +54,8 @@ protected override void OnStartup(StartupEventArgs e) RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () => { Logger.LogInfo("PowerToys Runner exited. Exiting ColorPicker"); - Environment.Exit(0); + NativeThreadCTS.Cancel(); + Dispatcher.Invoke(Shutdown); }); } else diff --git a/src/modules/colorPicker/ColorPickerUI/Controls/ColorFormatControl.xaml.cs b/src/modules/colorPicker/ColorPickerUI/Controls/ColorFormatControl.xaml.cs index 882450db3ff..cd98b064329 100644 --- a/src/modules/colorPicker/ColorPickerUI/Controls/ColorFormatControl.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/Controls/ColorFormatControl.xaml.cs @@ -7,7 +7,6 @@ using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Controls; -using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using ColorPicker.Helpers; diff --git a/src/modules/colorPicker/ColorPickerUI/Controls/HSVColor.cs b/src/modules/colorPicker/ColorPickerUI/Controls/HSVColor.cs index 10473f2f76d..4c77560e6f8 100644 --- a/src/modules/colorPicker/ColorPickerUI/Controls/HSVColor.cs +++ b/src/modules/colorPicker/ColorPickerUI/Controls/HSVColor.cs @@ -3,11 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Animation; namespace ColorPicker.Controls { diff --git a/src/modules/colorPicker/ColorPickerUI/Converters/ColorToBrushConverter.cs b/src/modules/colorPicker/ColorPickerUI/Converters/ColorToBrushConverter.cs index d46c52055f7..109255b0fa1 100644 --- a/src/modules/colorPicker/ColorPickerUI/Converters/ColorToBrushConverter.cs +++ b/src/modules/colorPicker/ColorPickerUI/Converters/ColorToBrushConverter.cs @@ -4,7 +4,6 @@ using System; using System.Globalization; -using System.Windows; using System.Windows.Data; using System.Windows.Media; diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/NativeEventWaiter.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/NativeEventWaiter.cs deleted file mode 100644 index 3f95eeaea6b..00000000000 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/NativeEventWaiter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading; -using System.Windows; - -namespace ColorPicker.Helpers -{ - public static class NativeEventWaiter - { - public static void WaitForEventLoop(string eventName, Action callback) - { - new Thread(() => - { - var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); - while (true) - { - if (eventHandle.WaitOne()) - { - Logger.LogInfo($"Successfully waited for {eventName}"); - Application.Current.Dispatcher.Invoke(callback); - } - } - }).Start(); - } - } -} diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs index 15af9e47ab6..a67c8b73595 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs @@ -7,11 +7,8 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Windows; using System.Windows.Media.Imaging; -using ColorPicker.Telemetry; using ColorPicker.ViewModelContracts; -using Microsoft.PowerToys.Telemetry; namespace ColorPicker.Helpers { diff --git a/src/modules/colorPicker/ColorPickerUI/Keyboard/KeyboardMonitor.cs b/src/modules/colorPicker/ColorPickerUI/Keyboard/KeyboardMonitor.cs index 8daf16a7342..6f121f589cf 100644 --- a/src/modules/colorPicker/ColorPickerUI/Keyboard/KeyboardMonitor.cs +++ b/src/modules/colorPicker/ColorPickerUI/Keyboard/KeyboardMonitor.cs @@ -8,10 +8,7 @@ using System.Windows.Input; using ColorPicker.Helpers; using ColorPicker.Settings; -using ColorPicker.Telemetry; -using Microsoft.PowerToys.Settings.UI.Library.Enumerations; using Microsoft.PowerToys.Settings.UI.Library.Utilities; -using Microsoft.PowerToys.Telemetry; using static ColorPicker.NativeMethods; namespace ColorPicker.Keyboard diff --git a/src/modules/colorPicker/ColorPickerUI/Program.cs b/src/modules/colorPicker/ColorPickerUI/Program.cs index 654e158d15e..65808b1544a 100644 --- a/src/modules/colorPicker/ColorPickerUI/Program.cs +++ b/src/modules/colorPicker/ColorPickerUI/Program.cs @@ -5,7 +5,6 @@ using System; using ColorPicker.Helpers; using ColorPicker.Mouse; - using ColorPickerUI; namespace ColorPicker diff --git a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs index 3d78f7c2eb2..0f8732db6b9 100644 --- a/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs +++ b/src/modules/colorPicker/ColorPickerUI/Telemetry/ColorPickerSession.cs @@ -2,7 +2,6 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Diagnostics.Tracing; using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry.Events; diff --git a/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs b/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs index ceeab364e69..a4fffec6bcb 100644 --- a/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs +++ b/src/modules/colorPicker/ColorPickerUI/ViewModels/MainViewModel.cs @@ -4,7 +4,6 @@ using System; using System.ComponentModel.Composition; -using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Media; @@ -13,11 +12,9 @@ using ColorPicker.Keyboard; using ColorPicker.Mouse; using ColorPicker.Settings; -using ColorPicker.Telemetry; using ColorPicker.ViewModelContracts; +using Common.UI; using interop; -using Microsoft.PowerToys.Settings.UI.Library.Enumerations; -using Microsoft.PowerToys.Telemetry; namespace ColorPicker.ViewModels { @@ -49,13 +46,24 @@ public MainViewModel( ZoomWindowHelper zoomWindowHelper, AppStateHandler appStateHandler, KeyboardMonitor keyboardMonitor, - IUserSettings userSettings) + IUserSettings userSettings, + CancellationToken exitToken) { _zoomWindowHelper = zoomWindowHelper; _appStateHandler = appStateHandler; _userSettings = userSettings; - NativeEventWaiter.WaitForEventLoop(Constants.ShowColorPickerSharedEvent(), _appStateHandler.StartUserSession); - NativeEventWaiter.WaitForEventLoop(Constants.ColorPickerSendSettingsTelemetryEvent(), _userSettings.SendSettingsTelemetry); + + NativeEventWaiter.WaitForEventLoop( + Constants.ShowColorPickerSharedEvent(), + _appStateHandler.StartUserSession, + Application.Current.Dispatcher, + exitToken); + + NativeEventWaiter.WaitForEventLoop( + Constants.ColorPickerSendSettingsTelemetryEvent(), + _userSettings.SendSettingsTelemetry, + Application.Current.Dispatcher, + exitToken); if (mouseInfoProvider != null) { diff --git a/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs b/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs index 8434b8879d4..d70514cf80f 100644 --- a/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs @@ -2,7 +2,6 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.ComponentModel; using System.Windows; namespace ColorPicker diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index e691af206d7..e058fbe5db9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -5,9 +5,9 @@ using System; using System.Diagnostics; using System.Threading; -using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using System.Windows.Threading; using Common.UI; using FancyZonesEditor.Logs; using FancyZonesEditor.Utils; @@ -35,10 +35,6 @@ public partial class App : Application, IDisposable private ThemeManager _themeManager; - private EventWaitHandle _eventHandle; - - private Thread _exitWaitThread; - public static bool DebugMode { get @@ -50,6 +46,8 @@ public static bool DebugMode private static bool _debugMode; private bool _isDisposed; + private CancellationTokenSource NativeThreadCTS { get; set; } + [Conditional("DEBUG")] private void DebugModeCheck() { @@ -59,27 +57,29 @@ private void DebugModeCheck() public App() { // DebugModeCheck(); + NativeThreadCTS = new CancellationTokenSource(); FancyZonesEditorIO = new FancyZonesEditorIO(); Overlay = new Overlay(); MainWindowSettings = new MainWindowSettingsModel(); - _exitWaitThread = new Thread(App_WaitExit); - _exitWaitThread.Start(); + App_WaitExit(); } private void OnStartup(object sender, StartupEventArgs e) { AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + _themeManager = new ThemeManager(this); + + var parseResult = FancyZonesEditorIO.ParseParams(); + RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () => { Logger.LogInfo("Runner exited"); - Environment.Exit(0); + NativeThreadCTS.Cancel(); + Application.Current.Dispatcher.Invoke(Application.Current.Shutdown); }); - _themeManager = new ThemeManager(this); - - var parseResult = FancyZonesEditorIO.ParseParams(); if (!parseResult.Result) { Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData); @@ -122,26 +122,23 @@ private void OnStartup(object sender, StartupEventArgs e) private void OnExit(object sender, ExitEventArgs e) { + NativeThreadCTS.Cancel(); Dispose(); - if (_eventHandle != null) - { - _eventHandle.Set(); - } - - _exitWaitThread.Join(); - Logger.LogInfo("FancyZones Editor exited"); } private void App_WaitExit() { - _eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, interop.Constants.FZEExitEvent()); - if (_eventHandle.WaitOne()) + NativeEventWaiter.WaitForEventLoop( + interop.Constants.FZEExitEvent(), + () => { Logger.LogInfo("Exit event triggered"); - Environment.Exit(0); - } + Application.Current.Shutdown(); + }, + Current.Dispatcher, + NativeThreadCTS.Token); } public void App_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) diff --git a/src/modules/launcher/PowerLauncher/App.xaml.cs b/src/modules/launcher/PowerLauncher/App.xaml.cs index 4aabaac94f3..551cb1bfad4 100644 --- a/src/modules/launcher/PowerLauncher/App.xaml.cs +++ b/src/modules/launcher/PowerLauncher/App.xaml.cs @@ -72,13 +72,16 @@ public static void Main() using (var application = new App()) { application.InitializeComponent(); + NativeEventWaiter.WaitForEventLoop( Constants.RunExitEvent(), () => - { - Log.Warn("RunExitEvent was signaled. Exiting PowerToys", typeof(App)); - ExitPowerToys(application); - }, NativeThreadCTS.Token); + { + Log.Warn("RunExitEvent was signaled. Exiting PowerToys", typeof(App)); + ExitPowerToys(application); + }, + Application.Current.Dispatcher, + NativeThreadCTS.Token); if (powerToysPid != 0) { diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 515fd949326..0229a3c9760 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; +using Common.UI; using interop; using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerToys.Telemetry; @@ -52,7 +53,11 @@ public MainWindow(PowerToysRunSettings settings, MainViewModel mainVM, Cancellat _firstDeleteTimer.Elapsed += CheckForFirstDelete; _firstDeleteTimer.Interval = 1000; - NativeEventWaiter.WaitForEventLoop(Constants.RunSendSettingsTelemetryEvent(), SendSettingsTelemetry, _nativeWaiterCancelToken); + NativeEventWaiter.WaitForEventLoop( + Constants.RunSendSettingsTelemetryEvent(), + SendSettingsTelemetry, + Application.Current.Dispatcher, + _nativeWaiterCancelToken); } private void SendSettingsTelemetry() @@ -701,7 +706,15 @@ public void Dispose() private void OnClosed(object sender, EventArgs e) { - _hwndSource.RemoveHook(ProcessWindowMessages); + try + { + _hwndSource.RemoveHook(ProcessWindowMessages); + } + catch (Exception ex) + { + Log.Exception($"Exception when trying to Remove hook", ex, ex.GetType()); + } + _hwndSource = null; } } diff --git a/src/modules/launcher/PowerLauncher/PublicAPIInstance.cs b/src/modules/launcher/PowerLauncher/PublicAPIInstance.cs index 116163e36bd..b3a06879162 100644 --- a/src/modules/launcher/PowerLauncher/PublicAPIInstance.cs +++ b/src/modules/launcher/PowerLauncher/PublicAPIInstance.cs @@ -49,19 +49,6 @@ public void ChangeQuery(string query, bool requery = false) _mainVM.ChangeQueryText(query, requery); } - public void RestartApp() - { - _mainVM.MainWindowVisibility = Visibility.Hidden; - - // we must manually save - // UpdateManager.RestartApp() will call Environment.Exit(0) - // which will cause ungraceful exit - SaveAppAllSettings(); - - // Todo : Implement logic to restart this app. - Environment.Exit(0); - } - public void CheckForNewUpdate() { // _settingsVM.UpdateApp(); diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 95be3d16cdb..3b67c71226f 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -12,6 +12,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Threading; +using Common.UI; using interop; using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerToys.Telemetry; @@ -93,12 +94,12 @@ public void RegisterHotkey(IntPtr hwnd) Log.Info("RegisterHotkey()", GetType()); // Allow OOBE to call PowerToys Run. - NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherSharedEvent(), OnHotkey, _nativeWaiterCancelToken); + NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherSharedEvent(), OnHotkey, Application.Current.Dispatcher, _nativeWaiterCancelToken); if (_settings.StartedFromPowerToysRunner) { // Allow runner to call PowerToys Run from the centralized keyboard hook. - NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherCentralizedHookSharedEvent(), OnCentralizedKeyboardHookHotKey, _nativeWaiterCancelToken); + NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherCentralizedHookSharedEvent(), OnCentralizedKeyboardHookHotKey, Application.Current.Dispatcher, _nativeWaiterCancelToken); } _settings.PropertyChanged += (s, e) => diff --git a/src/modules/launcher/Wox.Plugin/IPublicAPI.cs b/src/modules/launcher/Wox.Plugin/IPublicAPI.cs index 2fcc1e4f180..3e913f0e3f6 100644 --- a/src/modules/launcher/Wox.Plugin/IPublicAPI.cs +++ b/src/modules/launcher/Wox.Plugin/IPublicAPI.cs @@ -23,11 +23,6 @@ public interface IPublicAPI /// void ChangeQuery(string query, bool requery = false); - /// - /// Restart Wox - /// - void RestartApp(); - /// /// Remove user selected history item and refresh/requery ///