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

@@ -43,6 +43,9 @@
<ItemGroup>
<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="System.CommandLine" Version="2.0.0-beta1.20071.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />

View File

@@ -7,17 +7,20 @@ using System.Collections.Generic;
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);
/// <summary>
/// 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 @@ namespace Awake.Core
_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 @@ namespace Awake.Core
/// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns>
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 @@ namespace Awake.Core
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 @@ namespace Awake.Core
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 @@ namespace Awake.Core
{
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 @@ namespace Awake.Core
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 @@ 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.")]
public static IEnumerable<IntPtr> EnumerateWindowsForProcess(int processId)
internal static IEnumerable<HWND> EnumerateWindowsForProcess(int processId)
{
var handles = new List<IntPtr>();
IntPtr hCurrentWnd = IntPtr.Zero;
var handles = new List<HWND>();
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 @@ namespace Awake.Core
}
[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");
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<char> 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<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.
// 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,
}
}

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.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 @@ namespace Awake.Core
{
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 @@ namespace Awake.Core
((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 @@ namespace Awake.Core
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
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,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.")]
public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts)
{
if (TrayMenu != IntPtr.Zero)
{
var destructionStatus = NativeMethods.DestroyMenu(TrayMenu);
if (destructionStatus != true)
{
_log.Error("Failed to destroy tray menu and free up memory.");
}
}
TrayMenu = new DestroyMenuSafeHandle(PInvoke.CreatePopupMenu());
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");
if (!TrayMenu.IsInvalid)
{
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");
}
// In case there are no tray shortcuts defined for the application default to a
// reasonable initial set.
@@ -123,18 +114,18 @@ namespace Awake.Core
}
// 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;
}

View File

@@ -6,9 +6,11 @@ using System;
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 @@ namespace Awake.Core
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 @@ namespace Awake.Core
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 @@ namespace Awake.Core
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)

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.
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
@@ -17,11 +16,15 @@ using System.Text.Json;
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 @@ namespace Awake
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 @@ namespace Awake
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 @@ namespace Awake
// 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<bool>? configOption = new (
var configOption = new Option<bool>(
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 @@ namespace Awake
configOption.Required = false;
Option<bool>? displayOption = new (
var displayOption = new Option<bool>(
aliases: new[] { "--display-on", "-d" },
getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.")
@@ -113,7 +116,7 @@ namespace Awake
displayOption.Required = false;
Option<uint>? timeOption = new (
var timeOption = new Option<uint>(
aliases: new[] { "--time-limit", "-t" },
getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.")
@@ -126,7 +129,7 @@ namespace Awake
timeOption.Required = false;
Option<int>? pidOption = new (
var pidOption = new Option<int>(
aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0,
description: $"Bind the execution of {InternalConstants.AppName} to another process.")
@@ -156,23 +159,23 @@ namespace Awake
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 @@ namespace Awake
// 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 @@ namespace Awake
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);
});
}