Don't use Environment.Exit (#20532)

* [Awake] Don't use Process.Exit and move to CsWin32

* [PowerLauncher] Remove unused API

* [ColorPicker] Use cancellable NativeEventWaiter + cleanup using

* [TextExtractor] Don't use Environment.Exit

* [MeasureTool] Don't use Environment.Exit(0);

* [FZE] don't use Environment.Exit and fix WaitForPowerToysRunner
This commit is contained in:
Andrey Nekrasov
2022-09-13 19:25:19 +03:00
committed by GitHub
parent cba6507d2b
commit 09f4dead7f
36 changed files with 258 additions and 480 deletions

View File

@@ -60,7 +60,6 @@ APeriod
api api
APIENTRY APIENTRY
APIIs APIIs
Apm
APPBARDATA APPBARDATA
appdata appdata
APPICON APPICON
@@ -130,7 +129,6 @@ Avanc
Awaitable Awaitable
awakeness awakeness
awakeversion awakeversion
AWAYMODE
AYUV AYUV
backend backend
backtracer backtracer
@@ -498,7 +496,6 @@ dvr
DVSD DVSD
DVSL DVSL
DVTARGETDEVICE DVTARGETDEVICE
dwhkl
DWINRT DWINRT
dwl dwl
dwm dwm
@@ -728,8 +725,6 @@ hhk
HHmmss HHmmss
HHOOK HHOOK
hhx hhx
Hiber
Hiberboot
HIBYTE HIBYTE
HICON HICON
HIDEWINDOW HIDEWINDOW
@@ -907,7 +902,6 @@ inheritdoc
initguid initguid
Inkscape Inkscape
Inlines Inlines
Inlining
inorder inorder
INotification INotification
INotify INotify
@@ -1037,7 +1031,6 @@ jxr
jyuwono jyuwono
KBDLLHOOKSTRUCT KBDLLHOOKSTRUCT
kbm kbm
KCode
KEYBDINPUT KEYBDINPUT
keybindings keybindings
keyboardeventhandlers keyboardeventhandlers
@@ -1161,7 +1154,6 @@ lpsz
lpt lpt
LPTHREAD LPTHREAD
LPTOP LPTOP
lptpm
LPTSTR LPTSTR
LPVOID LPVOID
LPW LPW
@@ -1226,7 +1218,6 @@ Melman
memcmp memcmp
memcpy memcpy
memset memset
MENUBREAK
MENUITEMINFO MENUITEMINFO
MENUITEMINFOW MENUITEMINFOW
Metadatas Metadatas
@@ -1432,7 +1423,6 @@ ntdll
NTFS NTFS
NTSTATUS NTSTATUS
nuget nuget
nuint
nullonfailure nullonfailure
nullopt nullopt
nullptr nullptr
@@ -1494,7 +1484,6 @@ overlaywindow
Overridable Overridable
Oversampling Oversampling
OWNDC OWNDC
OWNERDRAW
PACL PACL
pagos pagos
PAINTSTRUCT PAINTSTRUCT
@@ -1536,6 +1525,7 @@ pfo
pft pft
pgp pgp
pguid pguid
PHANDLER
phbm phbm
phbmp phbmp
phwnd phwnd
@@ -1938,6 +1928,7 @@ sse
ssf ssf
ssh ssh
sstream sstream
stackalloc
STACKFRAME STACKFRAME
stackoverflow stackoverflow
stackpanel stackpanel
@@ -2237,14 +2228,12 @@ VIDEOINFOHEADER
viewbox viewbox
viewmodel viewmodel
vih vih
Virt
virtualization virtualization
Virtualizing Virtualizing
visiblecolorformats visiblecolorformats
Visibletrue Visibletrue
visualbrush visualbrush
visualstudio visualstudio
viter
VKey VKey
VKTAB VKTAB
vmovl vmovl

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic; using System.Collections.Generic;
using ControlzEx.Theming; using ControlzEx.Theming;
namespace Common.UI namespace Common.UI

View File

@@ -3,26 +3,24 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Reflection;
using System.Threading; 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 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(() => new Thread(() =>
{ {
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, eventName);
while (true) while (true)
{ {
if (WaitHandle.WaitAny(new WaitHandle[] { cancel.WaitHandle, eventHandle }) == 1) if (WaitHandle.WaitAny(new WaitHandle[] { cancel.WaitHandle, eventHandle }) == 1)
{ {
Log.Info($"Successfully waited for {eventName}", MethodBase.GetCurrentMethod().DeclaringType); dispatcher.BeginInvoke(callback);
Application.Current.Dispatcher.Invoke(callback);
} }
else else
{ {

View File

@@ -5,6 +5,7 @@
using System; using System;
using ManagedCommon; using ManagedCommon;
using MeasureToolUI.Helpers; using MeasureToolUI.Helpers;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
namespace MeasureToolUI namespace MeasureToolUI
@@ -36,9 +37,10 @@ namespace MeasureToolUI
{ {
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid)) if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
{ {
var dispatcher = DispatcherQueue.GetForCurrentThread();
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () => RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
{ {
Environment.Exit(0); dispatcher.TryEnqueue(App.Current.Exit);
}); });
} }
} }
@@ -51,7 +53,8 @@ namespace MeasureToolUI
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError($"MeasureToolCore failed to initialize: {ex}"); Logger.LogError($"MeasureToolCore failed to initialize: {ex}");
Environment.Exit(1); App.Current.Exit();
return;
} }
_window = new MainWindow(core); _window = new MainWindow(core);

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using ManagedCommon; using ManagedCommon;
@@ -23,6 +22,13 @@ public partial class App : Application, IDisposable
private Mutex? _instanceMutex; private Mutex? _instanceMutex;
private int _powerToysRunnerPid; private int _powerToysRunnerPid;
private CancellationTokenSource NativeThreadCTS { get; set; }
public App()
{
NativeThreadCTS = new CancellationTokenSource();
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
@@ -37,7 +43,7 @@ public partial class App : Application, IDisposable
{ {
Logger.LogWarning("Another running TextExtractor instance was detected. Exiting TextExtractor"); Logger.LogWarning("Another running TextExtractor instance was detected. Exiting TextExtractor");
_instanceMutex = null; _instanceMutex = null;
Environment.Exit(0); Shutdown();
return; return;
} }
@@ -51,10 +57,11 @@ public partial class App : Application, IDisposable
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () => RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
{ {
Logger.LogInfo("PowerToys Runner exited. Exiting TextExtractor"); Logger.LogInfo("PowerToys Runner exited. Exiting TextExtractor");
Environment.Exit(0); NativeThreadCTS.Cancel();
Application.Current.Dispatcher.Invoke(() => Shutdown());
}); });
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker()); var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
eventMonitor = new EventMonitor(); eventMonitor = new EventMonitor(Application.Current.Dispatcher, NativeThreadCTS.Token);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -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();
}
}
}

View File

@@ -4,6 +4,7 @@
using System; using System;
using System.Windows.Interop; using System.Windows.Interop;
using Common.UI;
using interop; using interop;
using PowerOCR.Helpers; using PowerOCR.Helpers;
using PowerOCR.Utilities; using PowerOCR.Utilities;
@@ -16,9 +17,9 @@ namespace PowerOCR.Keyboard
/// </summary> /// </summary>
internal class EventMonitor 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() public void StartOCRSession()

View File

@@ -43,6 +43,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.46-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NLog" Version="4.7.13" /> <PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20071.2" /> <PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20071.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />

View File

@@ -7,17 +7,20 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using Awake.Core.Models; using Awake.Core.Models;
using Microsoft.Win32; using Microsoft.Win32;
using NLog; 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 namespace Awake.Core
{ {
public delegate bool ConsoleEventHandler(ControlType ctrlType);
/// <summary> /// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts /// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase. /// of the codebase.
@@ -25,9 +28,6 @@ namespace Awake.Core
public class APIHelper public class APIHelper
{ {
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion"; 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 readonly Logger _log;
private static CancellationTokenSource _tokenSource; private static CancellationTokenSource _tokenSource;
@@ -43,21 +43,21 @@ namespace Awake.Core
_tokenSource = new CancellationTokenSource(); _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() public static void AllocateConsole()
{ {
_log.Debug("Bootstrapping the console allocation routine."); _log.Debug("Bootstrapping the console allocation routine.");
NativeMethods.AllocConsole(); PInvoke.AllocConsole();
_log.Debug($"Console allocation result: {Marshal.GetLastWin32Error()}"); _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()}"); _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()}"); _log.Debug($"SetStdHandle result: {Marshal.GetLastWin32Error()}");
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true }); Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
@@ -70,11 +70,11 @@ namespace Awake.Core
/// </summary> /// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param> /// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns> /// <returns>true if successful, false if failed</returns>
private static bool SetAwakeState(ExecutionState state) private static bool SetAwakeState(EXECUTION_STATE state)
{ {
try try
{ {
var stateResult = NativeMethods.SetThreadExecutionState(state); var stateResult = PInvoke.SetThreadExecutionState(state);
return stateResult != 0; return stateResult != 0;
} }
catch catch
@@ -160,18 +160,18 @@ namespace Awake.Core
bool success; bool success;
if (keepDisplayOn) 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 else
{ {
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS); success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
} }
try try
{ {
if (success) 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 }); WaitHandle.WaitAny(new[] { _threadToken.WaitHandle });
@@ -186,28 +186,35 @@ namespace Awake.Core
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {
// Task was clearly cancelled. // 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; return success;
} }
} }
internal static void CompleteExit(int exitCode, bool force = false) internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
{ {
APIHelper.SetNoKeepAwake(); SetNoKeepAwake();
TrayHelper.ClearTray();
// Because we are running a message loop for the tray, we can't just use Environment.Exit, HWND windowHandle = GetHiddenWindow();
// but have to make sure that we properly send the termination message.
IntPtr windowHandle = APIHelper.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) 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 @@ namespace Awake.Core
{ {
if (keepDisplayOn) 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 else
{ {
success = SetAwakeState(ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS); success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
} }
if (success) 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) => _timedLoopTimer.Elapsed += (s, e) =>
{ {
_tokenSource.Cancel(); _tokenSource.Cancel();
@@ -262,7 +269,7 @@ namespace Awake.Core
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {
// Task was clearly cancelled. // 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; return success;
} }
} }
@@ -294,15 +301,20 @@ namespace Awake.Core
} }
[SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Function returns DWORD value that identifies the current thread, but we do not need it.")] [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<IntPtr> EnumerateWindowsForProcess(int processId) internal static IEnumerable<HWND> EnumerateWindowsForProcess(int processId)
{ {
var handles = new List<IntPtr>(); var handles = new List<HWND>();
IntPtr hCurrentWnd = IntPtr.Zero; var hCurrentWnd = HWND.Null;
do do
{ {
hCurrentWnd = NativeMethods.FindWindowEx(IntPtr.Zero, hCurrentWnd, null, null); hCurrentWnd = PInvoke.FindWindowEx(HWND.Null, hCurrentWnd, null as string, null);
NativeMethods.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId); uint targetProcessId = 0;
unsafe
{
PInvoke.GetWindowThreadProcessId(hCurrentWnd, &targetProcessId);
}
if (targetProcessId == processId) if (targetProcessId == processId)
{ {
handles.Add(hCurrentWnd); handles.Add(hCurrentWnd);
@@ -314,23 +326,30 @@ namespace Awake.Core
} }
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")] [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<IntPtr> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId); IEnumerable<HWND> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x"); var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
string targetClass = $"{InternalConstants.TrayWindowId}{domain}"; string targetClass = $"{InternalConstants.TrayWindowId}{domain}";
foreach (var handle in windowHandles) unsafe
{ {
StringBuilder className = new (256); var classNameLen = 256;
int classQueryResult = NativeMethods.GetClassName(handle, className, className.Capacity); Span<char> className = stackalloc char[classNameLen];
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase)) 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<string, int> GetDefaultTrayOptions() public static Dictionary<string, int> GetDefaultTrayOptions()

View File

@@ -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,
}
}

View File

@@ -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;
}
}

View File

@@ -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,
}
}

View File

@@ -2,14 +2,16 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Windows.Win32;
namespace Awake.Core.Models namespace Awake.Core.Models
{ {
internal enum TrayCommands : uint internal enum TrayCommands : uint
{ {
TC_DISPLAY_SETTING = NativeConstants.WM_USER + 1, TC_DISPLAY_SETTING = PInvoke.WM_USER + 1,
TC_MODE_PASSIVE = NativeConstants.WM_USER + 2, TC_MODE_PASSIVE = PInvoke.WM_USER + 2,
TC_MODE_INDEFINITE = NativeConstants.WM_USER + 3, TC_MODE_INDEFINITE = PInvoke.WM_USER + 3,
TC_EXIT = NativeConstants.WM_USER + 4, TC_EXIT = PInvoke.WM_USER + 4,
TC_TIME = NativeConstants.WM_USER + 5, TC_TIME = PInvoke.WM_USER + 5,
} }
} }

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -7,11 +7,16 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using Awake.Core.Models; using Awake.Core.Models;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using NLog; 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 CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
@@ -22,21 +27,18 @@ namespace Awake.Core
{ {
private static readonly Logger _log; 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 { get; set; }
private static NotifyIcon? _trayIcon;
private static NotifyIcon TrayIcon { get => _trayIcon; set => _trayIcon = value; }
static TrayHelper() static TrayHelper()
{ {
_log = LogManager.GetCurrentClassLogger(); _log = LogManager.GetCurrentClassLogger();
TrayMenu = new DestroyMenuSafeHandle();
TrayIcon = new NotifyIcon(); 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( Task.Factory.StartNew(
(tray) => (tray) =>
@@ -49,7 +51,7 @@ namespace Awake.Core
((NotifyIcon?)tray).ContextMenuStrip = contextMenu; ((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true; ((NotifyIcon?)tray).Visible = true;
((NotifyIcon?)tray).MouseClick += TrayClickHandler; ((NotifyIcon?)tray).MouseClick += TrayClickHandler;
Application.AddMessageFilter(new TrayMessageFilter()); Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
Application.Run(); Application.Run();
_log.Info("Tray setup complete."); _log.Info("Tray setup complete.");
} }
@@ -74,21 +76,15 @@ namespace Awake.Core
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param> /// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
private static void TrayClickHandler(object? sender, MouseEventArgs e) 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); PInvoke.SetForegroundWindow(windowHandle);
NativeMethods.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, IntPtr.Zero); 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) internal static void SetTray(string text, AwakeSettings settings)
{ {
SetTray( SetTray(
@@ -101,19 +97,14 @@ namespace Awake.Core
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:Single line comments should begin with single space", Justification = "For debugging purposes - will remove later.")] [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<string, int> trayTimeShortcuts) public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts)
{ {
if (TrayMenu != IntPtr.Zero) TrayMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu());
{
var destructionStatus = NativeMethods.DestroyMenu(TrayMenu);
if (destructionStatus != true)
{
_log.Error("Failed to destroy tray menu and free up memory.");
}
}
TrayMenu = NativeMethods.CreatePopupMenu(); if (!TrayMenu.IsInvalid)
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); PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit");
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"); 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");
}
// In case there are no tray shortcuts defined for the application default to a // In case there are no tray shortcuts defined for the application default to a
// reasonable initial set. // reasonable initial set.
@@ -123,18 +114,18 @@ namespace Awake.Core
} }
// TODO: Make sure that this loads from JSON instead of being hard-coded. // 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++) 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(); var modeMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu(), false);
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)"); 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)");
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"); 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"); 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");
NativeMethods.InsertMenu(TrayMenu, 0, NativeConstants.MF_BYPOSITION | NativeConstants.MF_POPUP, (uint)modeMenu, "Mode"); PInvoke.InsertMenu(TrayMenu, 0, MENU_ITEM_FLAGS.MF_BYPOSITION | MENU_ITEM_FLAGS.MF_POPUP, (uint)modeMenu.DangerousGetHandle(), "Mode");
TrayIcon.Text = text; TrayIcon.Text = text;
} }

View File

@@ -6,9 +6,11 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using Awake.Core.Models; using Awake.Core.Models;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Windows.Win32;
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
@@ -20,8 +22,11 @@ namespace Awake.Core
private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; } private static SettingsUtils ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
public TrayMessageFilter() private static ManualResetEvent? _exitSignal;
public TrayMessageFilter(ManualResetEvent? exitSignal)
{ {
_exitSignal = exitSignal;
ModuleSettings = new SettingsUtils(); ModuleSettings = new SettingsUtils();
} }
@@ -31,12 +36,12 @@ namespace Awake.Core
switch (m.Msg) switch (m.Msg)
{ {
case (int)NativeConstants.WM_COMMAND: case (int)PInvoke.WM_COMMAND:
var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF; var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF;
switch (targetCommandIndex) switch (targetCommandIndex)
{ {
case (long)TrayCommands.TC_EXIT: case (long)TrayCommands.TC_EXIT:
ExitCommandHandler(); ExitCommandHandler(_exitSignal);
break; break;
case (long)TrayCommands.TC_DISPLAY_SETTING: case (long)TrayCommands.TC_DISPLAY_SETTING:
DisplaySettingCommandHandler(InternalConstants.AppName); DisplaySettingCommandHandler(InternalConstants.AppName);
@@ -68,9 +73,9 @@ namespace Awake.Core
return false; 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) private static void DisplaySettingCommandHandler(string moduleName)

View File

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

View File

@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.CommandLine; using System.CommandLine;
using System.CommandLine.Invocation; using System.CommandLine.Invocation;
using System.Diagnostics; using System.Diagnostics;
@@ -17,11 +16,15 @@ using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Awake.Core; using Awake.Core;
using Awake.Core.Models;
using interop; using interop;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry.Events;
using NLog; 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 CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return. #pragma warning disable CS8603 // Possible null reference return.
@@ -47,8 +50,8 @@ namespace Awake
private static Logger? _log; private static Logger? _log;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #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 PHANDLER_ROUTINE _handler;
private static SystemPowerCapabilities _powerCapabilities; 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. #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); private static ManualResetEvent _exitSignal = new ManualResetEvent(false);
@@ -63,7 +66,7 @@ namespace Awake
if (!instantiated) 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(); _settingsUtils = new SettingsUtils();
@@ -82,12 +85,12 @@ namespace Awake
// To make it easier to diagnose future issues, let's get the // To make it easier to diagnose future issues, let's get the
// system power capabilities and aggregate them in the log. // system power capabilities and aggregate them in the log.
NativeMethods.GetPwrCapabilities(out _powerCapabilities); PInvoke.GetPwrCapabilities(out _powerCapabilities);
_log.Info(JsonSerializer.Serialize(_powerCapabilities)); _log.Info(JsonSerializer.Serialize(_powerCapabilities));
_log.Info("Parsing parameters..."); _log.Info("Parsing parameters...");
Option<bool>? configOption = new ( var configOption = new Option<bool>(
aliases: new[] { "--use-pt-config", "-c" }, aliases: new[] { "--use-pt-config", "-c" },
getDefaultValue: () => false, getDefaultValue: () => false,
description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.") description: $"Specifies whether {InternalConstants.AppName} will be using the PowerToys configuration file for managing the state.")
@@ -100,7 +103,7 @@ namespace Awake
configOption.Required = false; configOption.Required = false;
Option<bool>? displayOption = new ( var displayOption = new Option<bool>(
aliases: new[] { "--display-on", "-d" }, aliases: new[] { "--display-on", "-d" },
getDefaultValue: () => true, getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.") description: "Determines whether the display should be kept awake.")
@@ -113,7 +116,7 @@ namespace Awake
displayOption.Required = false; displayOption.Required = false;
Option<uint>? timeOption = new ( var timeOption = new Option<uint>(
aliases: new[] { "--time-limit", "-t" }, aliases: new[] { "--time-limit", "-t" },
getDefaultValue: () => 0, getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.") description: "Determines the interval, in seconds, during which the computer is kept awake.")
@@ -126,7 +129,7 @@ namespace Awake
timeOption.Required = false; timeOption.Required = false;
Option<int>? pidOption = new ( var pidOption = new Option<int>(
aliases: new[] { "--pid", "-p" }, aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0, getDefaultValue: () => 0,
description: $"Bind the execution of {InternalConstants.AppName} to another process.") description: $"Bind the execution of {InternalConstants.AppName} to another process.")
@@ -156,23 +159,23 @@ namespace Awake
return rootCommand.InvokeAsync(args).Result; 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}"); _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; 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); _log.Info(message);
APIHelper.CompleteExit(exitCode, force); APIHelper.CompleteExit(exitCode, exitSignal, force);
} }
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid) private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid)
{ {
_handler += new ConsoleEventHandler(ExitHandler); _handler += ExitHandler;
APIHelper.SetConsoleControlHandler(_handler, true); APIHelper.SetConsoleControlHandler(_handler, true);
if (pid == 0) if (pid == 0)
@@ -192,16 +195,15 @@ namespace Awake
// and instead watch for changes in the file. // and instead watch for changes in the file.
try try
{ {
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, Constants.AwakeExitEvent());
new Thread(() => new Thread(() =>
{ {
EventWaitHandle? eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.AwakeExitEvent()); if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1)
if (eventHandle.WaitOne())
{ {
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(); }).Start();
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"), _exitSignal);
TrayHelper.InitializeTray(InternalConstants.FullAppName, new Icon("modules/awake/images/awake.ico"));
string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName); string? settingsPath = _settingsUtils.GetSettingsFilePath(InternalConstants.AppName);
_log.Info($"Reading configuration file: {settingsPath}"); _log.Info($"Reading configuration file: {settingsPath}");
@@ -263,7 +265,7 @@ namespace Awake
RunnerHelper.WaitForPowerToysRunner(pid, () => RunnerHelper.WaitForPowerToysRunner(pid, () =>
{ {
_log.Info($"Triggered PID-based exit handler for PID {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);
}); });
} }

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.ComponentModel.Composition;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using ColorPicker.Helpers; using ColorPicker.Helpers;
@@ -23,8 +24,16 @@ namespace ColorPickerUI
private bool disposedValue; private bool disposedValue;
private ThemeManager _themeManager; private ThemeManager _themeManager;
private CancellationTokenSource NativeThreadCTS { get; set; }
[Export]
private static CancellationToken ExitToken { get; set; }
protected override void OnStartup(StartupEventArgs e) protected override void OnStartup(StartupEventArgs e)
{ {
NativeThreadCTS = new CancellationTokenSource();
ExitToken = NativeThreadCTS.Token;
_args = e?.Args; _args = e?.Args;
// allow only one instance of color picker // allow only one instance of color picker
@@ -33,7 +42,7 @@ namespace ColorPickerUI
{ {
Logger.LogWarning("There is ColorPicker instance running. Exiting Color Picker"); Logger.LogWarning("There is ColorPicker instance running. Exiting Color Picker");
_instanceMutex = null; _instanceMutex = null;
Environment.Exit(0); Shutdown(0);
return; return;
} }
@@ -45,7 +54,8 @@ namespace ColorPickerUI
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () => RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
{ {
Logger.LogInfo("PowerToys Runner exited. Exiting ColorPicker"); Logger.LogInfo("PowerToys Runner exited. Exiting ColorPicker");
Environment.Exit(0); NativeThreadCTS.Cancel();
Dispatcher.Invoke(Shutdown);
}); });
} }
else else

View File

@@ -7,7 +7,6 @@ using System.Globalization;
using System.Windows; using System.Windows;
using System.Windows.Automation.Peers; using System.Windows.Automation.Peers;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
using ColorPicker.Helpers; using ColorPicker.Helpers;

View File

@@ -3,11 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation;
namespace ColorPicker.Controls namespace ColorPicker.Controls
{ {

View File

@@ -4,7 +4,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Media; using System.Windows.Media;

View File

@@ -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();
}
}
}

View File

@@ -7,11 +7,8 @@ using System.ComponentModel.Composition;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using ColorPicker.Telemetry;
using ColorPicker.ViewModelContracts; using ColorPicker.ViewModelContracts;
using Microsoft.PowerToys.Telemetry;
namespace ColorPicker.Helpers namespace ColorPicker.Helpers
{ {

View File

@@ -8,10 +8,7 @@ using System.ComponentModel.Composition;
using System.Windows.Input; using System.Windows.Input;
using ColorPicker.Helpers; using ColorPicker.Helpers;
using ColorPicker.Settings; using ColorPicker.Settings;
using ColorPicker.Telemetry;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.PowerToys.Settings.UI.Library.Utilities; using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Telemetry;
using static ColorPicker.NativeMethods; using static ColorPicker.NativeMethods;
namespace ColorPicker.Keyboard namespace ColorPicker.Keyboard

View File

@@ -5,7 +5,6 @@
using System; using System;
using ColorPicker.Helpers; using ColorPicker.Helpers;
using ColorPicker.Mouse; using ColorPicker.Mouse;
using ColorPickerUI; using ColorPickerUI;
namespace ColorPicker namespace ColorPicker

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics.Tracing; using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events; using Microsoft.PowerToys.Telemetry.Events;

View File

@@ -4,7 +4,6 @@
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
@@ -13,11 +12,9 @@ using ColorPicker.Helpers;
using ColorPicker.Keyboard; using ColorPicker.Keyboard;
using ColorPicker.Mouse; using ColorPicker.Mouse;
using ColorPicker.Settings; using ColorPicker.Settings;
using ColorPicker.Telemetry;
using ColorPicker.ViewModelContracts; using ColorPicker.ViewModelContracts;
using Common.UI;
using interop; using interop;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.PowerToys.Telemetry;
namespace ColorPicker.ViewModels namespace ColorPicker.ViewModels
{ {
@@ -49,13 +46,24 @@ namespace ColorPicker.ViewModels
ZoomWindowHelper zoomWindowHelper, ZoomWindowHelper zoomWindowHelper,
AppStateHandler appStateHandler, AppStateHandler appStateHandler,
KeyboardMonitor keyboardMonitor, KeyboardMonitor keyboardMonitor,
IUserSettings userSettings) IUserSettings userSettings,
CancellationToken exitToken)
{ {
_zoomWindowHelper = zoomWindowHelper; _zoomWindowHelper = zoomWindowHelper;
_appStateHandler = appStateHandler; _appStateHandler = appStateHandler;
_userSettings = userSettings; _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) if (mouseInfoProvider != null)
{ {

View File

@@ -2,7 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Windows; using System.Windows;
namespace ColorPicker namespace ColorPicker

View File

@@ -5,9 +5,9 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
using Common.UI; using Common.UI;
using FancyZonesEditor.Logs; using FancyZonesEditor.Logs;
using FancyZonesEditor.Utils; using FancyZonesEditor.Utils;
@@ -35,10 +35,6 @@ namespace FancyZonesEditor
private ThemeManager _themeManager; private ThemeManager _themeManager;
private EventWaitHandle _eventHandle;
private Thread _exitWaitThread;
public static bool DebugMode public static bool DebugMode
{ {
get get
@@ -50,6 +46,8 @@ namespace FancyZonesEditor
private static bool _debugMode; private static bool _debugMode;
private bool _isDisposed; private bool _isDisposed;
private CancellationTokenSource NativeThreadCTS { get; set; }
[Conditional("DEBUG")] [Conditional("DEBUG")]
private void DebugModeCheck() private void DebugModeCheck()
{ {
@@ -59,27 +57,29 @@ namespace FancyZonesEditor
public App() public App()
{ {
// DebugModeCheck(); // DebugModeCheck();
NativeThreadCTS = new CancellationTokenSource();
FancyZonesEditorIO = new FancyZonesEditorIO(); FancyZonesEditorIO = new FancyZonesEditorIO();
Overlay = new Overlay(); Overlay = new Overlay();
MainWindowSettings = new MainWindowSettingsModel(); MainWindowSettings = new MainWindowSettingsModel();
_exitWaitThread = new Thread(App_WaitExit); App_WaitExit();
_exitWaitThread.Start();
} }
private void OnStartup(object sender, StartupEventArgs e) private void OnStartup(object sender, StartupEventArgs e)
{ {
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Logger.LogInfo("Runner exited");
Environment.Exit(0);
});
_themeManager = new ThemeManager(this); _themeManager = new ThemeManager(this);
var parseResult = FancyZonesEditorIO.ParseParams(); var parseResult = FancyZonesEditorIO.ParseParams();
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
{
Logger.LogInfo("Runner exited");
NativeThreadCTS.Cancel();
Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
});
if (!parseResult.Result) if (!parseResult.Result)
{ {
Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData); Logger.LogError(ParsingErrorReportTag + ": " + parseResult.Message + "; " + ParsingErrorDataTag + ": " + parseResult.MalformedData);
@@ -122,26 +122,23 @@ namespace FancyZonesEditor
private void OnExit(object sender, ExitEventArgs e) private void OnExit(object sender, ExitEventArgs e)
{ {
NativeThreadCTS.Cancel();
Dispose(); Dispose();
if (_eventHandle != null)
{
_eventHandle.Set();
}
_exitWaitThread.Join();
Logger.LogInfo("FancyZones Editor exited"); Logger.LogInfo("FancyZones Editor exited");
} }
private void App_WaitExit() private void App_WaitExit()
{ {
_eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, interop.Constants.FZEExitEvent()); NativeEventWaiter.WaitForEventLoop(
if (_eventHandle.WaitOne()) interop.Constants.FZEExitEvent(),
() =>
{ {
Logger.LogInfo("Exit event triggered"); 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) public void App_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)

View File

@@ -72,13 +72,16 @@ namespace PowerLauncher
using (var application = new App()) using (var application = new App())
{ {
application.InitializeComponent(); application.InitializeComponent();
NativeEventWaiter.WaitForEventLoop( NativeEventWaiter.WaitForEventLoop(
Constants.RunExitEvent(), Constants.RunExitEvent(),
() => () =>
{ {
Log.Warn("RunExitEvent was signaled. Exiting PowerToys", typeof(App)); Log.Warn("RunExitEvent was signaled. Exiting PowerToys", typeof(App));
ExitPowerToys(application); ExitPowerToys(application);
}, NativeThreadCTS.Token); },
Application.Current.Dispatcher,
NativeThreadCTS.Token);
if (powerToysPid != 0) if (powerToysPid != 0)
{ {

View File

@@ -12,6 +12,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using Common.UI;
using interop; using interop;
using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerLauncher.Telemetry;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
@@ -52,7 +53,11 @@ namespace PowerLauncher
_firstDeleteTimer.Elapsed += CheckForFirstDelete; _firstDeleteTimer.Elapsed += CheckForFirstDelete;
_firstDeleteTimer.Interval = 1000; _firstDeleteTimer.Interval = 1000;
NativeEventWaiter.WaitForEventLoop(Constants.RunSendSettingsTelemetryEvent(), SendSettingsTelemetry, _nativeWaiterCancelToken); NativeEventWaiter.WaitForEventLoop(
Constants.RunSendSettingsTelemetryEvent(),
SendSettingsTelemetry,
Application.Current.Dispatcher,
_nativeWaiterCancelToken);
} }
private void SendSettingsTelemetry() private void SendSettingsTelemetry()
@@ -701,7 +706,15 @@ namespace PowerLauncher
private void OnClosed(object sender, EventArgs e) 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; _hwndSource = null;
} }
} }

View File

@@ -49,19 +49,6 @@ namespace Wox
_mainVM.ChangeQueryText(query, requery); _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() public void CheckForNewUpdate()
{ {
// _settingsVM.UpdateApp(); // _settingsVM.UpdateApp();

View File

@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading; using System.Windows.Threading;
using Common.UI;
using interop; using interop;
using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerLauncher.Telemetry;
using Microsoft.PowerToys.Telemetry; using Microsoft.PowerToys.Telemetry;
@@ -93,12 +94,12 @@ namespace PowerLauncher.ViewModel
Log.Info("RegisterHotkey()", GetType()); Log.Info("RegisterHotkey()", GetType());
// Allow OOBE to call PowerToys Run. // Allow OOBE to call PowerToys Run.
NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherSharedEvent(), OnHotkey, _nativeWaiterCancelToken); NativeEventWaiter.WaitForEventLoop(Constants.PowerLauncherSharedEvent(), OnHotkey, Application.Current.Dispatcher, _nativeWaiterCancelToken);
if (_settings.StartedFromPowerToysRunner) if (_settings.StartedFromPowerToysRunner)
{ {
// Allow runner to call PowerToys Run from the centralized keyboard hook. // 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) => _settings.PropertyChanged += (s, e) =>

View File

@@ -23,11 +23,6 @@ namespace Wox.Plugin
/// </param> /// </param>
void ChangeQuery(string query, bool requery = false); void ChangeQuery(string query, bool requery = false);
/// <summary>
/// Restart Wox
/// </summary>
void RestartApp();
/// <summary> /// <summary>
/// Remove user selected history item and refresh/requery /// Remove user selected history item and refresh/requery
/// </summary> /// </summary>