diff --git a/src/modules/awake/Awake/Awake.csproj b/src/modules/awake/Awake/Awake.csproj index 1ea7ab2b39b..604841f4f6b 100644 --- a/src/modules/awake/Awake/Awake.csproj +++ b/src/modules/awake/Awake/Awake.csproj @@ -35,6 +35,7 @@ PowerToys.GPOWrapper $(OutDir) false + app.manifest diff --git a/src/modules/awake/Awake/Core/ExtensionMethods.cs b/src/modules/awake/Awake/Core/ExtensionMethods.cs index f86e9812fa7..4435e6e428c 100644 --- a/src/modules/awake/Awake/Core/ExtensionMethods.cs +++ b/src/modules/awake/Awake/Core/ExtensionMethods.cs @@ -19,5 +19,17 @@ public static void AddRange(this ICollection target, IEnumerable source target.Add(element); } } + + public static string ToHumanReadableString(this TimeSpan timeSpan) + { + // Get days, hours, minutes, and seconds from the TimeSpan + int days = timeSpan.Days; + int hours = timeSpan.Hours; + int minutes = timeSpan.Minutes; + int seconds = timeSpan.Seconds; + + // Format the string based on the presence of days, hours, minutes, and seconds + return $"{days:D2}{Properties.Resources.AWAKE_LABEL_DAYS} {hours:D2}{Properties.Resources.AWAKE_LABEL_HOURS} {minutes:D2}{Properties.Resources.AWAKE_LABEL_MINUTES} {seconds:D2}{Properties.Resources.AWAKE_LABEL_SECONDS}"; + } } } diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index 473a4f21472..c5217dcdba3 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -39,6 +39,12 @@ public class Manager private static readonly BlockingCollection _stateQueue; + // Core icons used for the tray + private static readonly Icon _timedIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/timed.ico")); + private static readonly Icon _expirableIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico")); + private static readonly Icon _indefiniteIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/indefinite.ico")); + private static readonly Icon _disabledIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/disabled.ico")); + private static CancellationTokenSource _tokenSource; private static SettingsUtils? _moduleSettings; @@ -135,7 +141,7 @@ internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false) _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/indefinite.ico")), TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_INDEFINITE}]", _indefiniteIcon, TrayIconAction.Update); if (IsUsingPowerToysConfig) { @@ -172,14 +178,23 @@ internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDis Logger.LogInfo($"Starting expirable log for {expireAt}"); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION}]", new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico")), TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION} - {expireAt}]", _expirableIcon, TrayIconAction.Update); Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe( _ => { Logger.LogInfo($"Completed expirable keep-awake."); CancelExistingThread(); - SetPassiveKeepAwake(); + + if (IsUsingPowerToysConfig) + { + SetPassiveKeepAwake(); + } + else + { + Logger.LogInfo("Exiting after expirable keep awake."); + CompleteExit(Environment.ExitCode); + } }, _tokenSource.Token); } @@ -224,16 +239,40 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) Logger.LogInfo($"Timed keep awake started for {seconds} seconds."); _stateQueue.Add(ComputeAwakeState(keepDisplayOn)); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/timed.ico")), TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]", _timedIcon, TrayIconAction.Update); - Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe( - _ => - { - Logger.LogInfo($"Completed timed thread."); - CancelExistingThread(); - SetPassiveKeepAwake(); - }, - _tokenSource.Token); + var timerObservable = Observable.Timer(TimeSpan.FromSeconds(seconds)); + var intervalObservable = Observable.Interval(TimeSpan.FromSeconds(1)).TakeUntil(timerObservable); + + var combinedObservable = Observable.CombineLatest(intervalObservable, timerObservable.StartWith(0), (elapsedSeconds, _) => elapsedSeconds + 1); + + combinedObservable.Subscribe( + elapsedSeconds => + { + var timeRemaining = seconds - (uint)elapsedSeconds; + if (timeRemaining >= 0) + { + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_TIMED}]\n{TimeSpan.FromSeconds(timeRemaining).ToHumanReadableString()}", _timedIcon, TrayIconAction.Update); + } + }, + () => + { + Console.WriteLine("Completed timed thread."); + CancelExistingThread(); + + if (IsUsingPowerToysConfig) + { + // If we're using PowerToys settings, we need to make sure that + // we just switch over the Passive Keep-Awake. + SetPassiveKeepAwake(); + } + else + { + Logger.LogInfo("Exiting after timed keep-awake."); + CompleteExit(Environment.ExitCode); + } + }, + _tokenSource.Token); if (IsUsingPowerToysConfig) { @@ -264,9 +303,7 @@ internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true) /// Performs a clean exit from Awake. /// /// Exit code to exit with. - /// Exit signal tracking the state. - /// Determines whether to force exit and post a quitting message. - internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false) + internal static void CompleteExit(int exitCode) { SetPassiveKeepAwake(updateSettings: false); @@ -277,22 +314,12 @@ internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bo // Close the message window that we used for the tray. Bridge.SendMessage(TrayHelper.HiddenWindowHandle, Native.Constants.WM_CLOSE, 0, 0); - } - - if (force) - { - Bridge.PostQuitMessage(exitCode); - } - try - { - exitSignal?.Set(); Bridge.DestroyWindow(TrayHelper.HiddenWindowHandle); } - catch (Exception ex) - { - Logger.LogError($"Exit signal error ${ex}"); - } + + Bridge.PostQuitMessage(exitCode); + Environment.Exit(exitCode); } /// @@ -350,7 +377,7 @@ internal static void SetPassiveKeepAwake(bool updateSettings = true) CancelExistingThread(); - TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/disabled.ico")), TrayIconAction.Update); + TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_OFF}]", _disabledIcon, TrayIconAction.Update); if (IsUsingPowerToysConfig && updateSettings) { diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index eff05e7c5b8..00000a431f9 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -28,7 +28,6 @@ namespace Awake.Core internal static class TrayHelper { private static NotifyIconData _notifyIconData; - private static ManualResetEvent? _exitSignal; private static IntPtr _trayMenu; @@ -44,41 +43,49 @@ static TrayHelper() HiddenWindowHandle = IntPtr.Zero; } - public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal) + public static void InitializeTray(string text, Icon icon) { - _exitSignal = exitSignal; - CreateHiddenWindow(icon, text); } private static void ShowContextMenu(IntPtr hWnd) { - Bridge.SetForegroundWindow(hWnd); + if (TrayMenu != IntPtr.Zero) + { + Bridge.SetForegroundWindow(hWnd); - // Get the handle to the context menu associated with the tray icon - IntPtr hMenu = TrayMenu; + // Get the handle to the context menu associated with the tray icon + IntPtr hMenu = TrayMenu; - // Get the current cursor position - Bridge.GetCursorPos(out Models.Point cursorPos); + // Get the current cursor position + Bridge.GetCursorPos(out Models.Point cursorPos); - Bridge.ScreenToClient(hWnd, ref cursorPos); + Bridge.ScreenToClient(hWnd, ref cursorPos); - MenuInfo menuInfo = new() + MenuInfo menuInfo = new() + { + CbSize = (uint)Marshal.SizeOf(typeof(MenuInfo)), + FMask = Native.Constants.MIM_STYLE, + DwStyle = Native.Constants.MNS_AUTO_DISMISS, + }; + Bridge.SetMenuInfo(hMenu, ref menuInfo); + + // Display the context menu at the cursor position + Bridge.TrackPopupMenuEx( + hMenu, + Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON, + cursorPos.X, + cursorPos.Y, + hWnd, + IntPtr.Zero); + } + else { - CbSize = (uint)Marshal.SizeOf(typeof(MenuInfo)), - FMask = Native.Constants.MIM_STYLE, - DwStyle = Native.Constants.MNS_AUTO_DISMISS, - }; - Bridge.SetMenuInfo(hMenu, ref menuInfo); - - // Display the context menu at the cursor position - Bridge.TrackPopupMenuEx( - hMenu, - Native.Constants.TPM_LEFT_ALIGN | Native.Constants.TPM_BOTTOMALIGN | Native.Constants.TPM_LEFT_BUTTON, - cursorPos.X, - cursorPos.Y, - hWnd, - IntPtr.Zero); + // Tray menu was not initialized. Log the issue. + // This is normal when operating in "standalone mode" - that is, detached + // from the PowerToys configuration file. + Logger.LogError("Tried to create a context menu while the TrayMenu object is a null pointer. Normal when used in standalone mode."); + } } private static void CreateHiddenWindow(Icon icon, string text) @@ -159,30 +166,40 @@ internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIcon break; } - _notifyIconData = action == TrayIconAction.Add || action == TrayIconAction.Update - ? new NotifyIconData + if (action == TrayIconAction.Add || action == TrayIconAction.Update) + { + _notifyIconData = new NotifyIconData { CbSize = Marshal.SizeOf(typeof(NotifyIconData)), HWnd = hWnd, UId = 1000, UFlags = Native.Constants.NIF_ICON | Native.Constants.NIF_TIP | Native.Constants.NIF_MESSAGE, UCallbackMessage = (int)Native.Constants.WM_USER, - HIcon = icon!.Handle, + HIcon = icon?.Handle ?? IntPtr.Zero, SzTip = text, - } - : new NotifyIconData + }; + } + else if (action == TrayIconAction.Delete) + { + _notifyIconData = new NotifyIconData { CbSize = Marshal.SizeOf(typeof(NotifyIconData)), HWnd = hWnd, UId = 1000, UFlags = 0, }; + } if (!Bridge.Shell_NotifyIcon(message, ref _notifyIconData)) { int errorCode = Marshal.GetLastWin32Error(); throw new Win32Exception(errorCode, $"Failed to change tray icon. Action: {action} and error code: {errorCode}"); } + + if (action == TrayIconAction.Delete) + { + _notifyIconData = default; + } } private static void RunMessageLoop() @@ -218,32 +235,47 @@ private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lPar switch (targetCommandIndex) { case (uint)TrayCommands.TC_EXIT: - Manager.CompleteExit(0, _exitSignal, true); - break; + { + Manager.CompleteExit(Environment.ExitCode); + break; + } + case (uint)TrayCommands.TC_DISPLAY_SETTING: - Manager.SetDisplay(); - break; + { + Manager.SetDisplay(); + break; + } + case (uint)TrayCommands.TC_MODE_INDEFINITE: - Manager.SetIndefiniteKeepAwake(); - break; + { + AwakeSettings settings = Manager.ModuleSettings!.GetSettings(Constants.AppName); + Manager.SetIndefiniteKeepAwake(keepDisplayOn: settings.Properties.KeepDisplayOn); + break; + } + case (uint)TrayCommands.TC_MODE_PASSIVE: - Manager.SetPassiveKeepAwake(); - break; + { + Manager.SetPassiveKeepAwake(); + break; + } + default: - if (targetCommandIndex >= trayCommandsSize) { - AwakeSettings settings = Manager.ModuleSettings!.GetSettings(Constants.AppName); - if (settings.Properties.CustomTrayTimes.Count == 0) + if (targetCommandIndex >= trayCommandsSize) { - settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions()); + AwakeSettings settings = Manager.ModuleSettings!.GetSettings(Constants.AppName); + if (settings.Properties.CustomTrayTimes.Count == 0) + { + settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions()); + } + + int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME; + uint targetTime = (uint)settings.Properties.CustomTrayTimes.ElementAt(index).Value; + Manager.SetTimedKeepAwake(targetTime, keepDisplayOn: settings.Properties.KeepDisplayOn); } - int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME; - uint targetTime = (uint)settings.Properties.CustomTrayTimes.ElementAt(index).Value; - Manager.SetTimedKeepAwake(targetTime); + break; } - - break; } break; @@ -300,7 +332,7 @@ public static void SetTray(bool keepDisplayOn, AwakeMode mode, Dictionary trayT } } - private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShortcuts) + private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShortcuts, bool isChecked = false) { var awakeTimeMenu = Bridge.CreatePopupMenu(); for (int i = 0; i < trayTimeShortcuts.Count; i++) @@ -359,7 +395,7 @@ private static void CreateAwakeTimeSubMenu(Dictionary trayTimeShort Bridge.InsertMenu(awakeTimeMenu, (uint)i, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_TIME + (uint)i, trayTimeShortcuts.ElementAt(i).Key); } - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP, (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (isChecked == true ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL); } private static void InsertAwakeModeMenuItems(AwakeMode mode) diff --git a/src/modules/awake/Awake/Program.cs b/src/modules/awake/Awake/Program.cs index 09a660a3505..8f36e7a14b3 100644 --- a/src/modules/awake/Awake/Program.cs +++ b/src/modules/awake/Awake/Program.cs @@ -26,8 +26,6 @@ namespace Awake { internal sealed class Program { - private static readonly ManualResetEvent _exitSignal = new(false); - private static Mutex? _mutex; private static FileSystemWatcher? _watcher; private static SettingsUtils? _settingsUtils; @@ -47,6 +45,8 @@ internal sealed class Program internal static readonly string[] AliasesPidOption = ["--pid", "-p"]; internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"]; + private static readonly Icon _defaultAwakeIcon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")); + private static int Main(string[] args) { _settingsUtils = new SettingsUtils(); @@ -54,15 +54,17 @@ private static int Main(string[] args) Logger.InitializeLogger(Path.Combine("\\", Core.Constants.AppName, "Logs")); + AppDomain.CurrentDomain.UnhandledException += AwakeUnhandledExceptionCatcher; + if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAwakeEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) { - Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1, _exitSignal, true); + Exit("PowerToys.Awake tried to start with a group policy setting that disables the tool. Please contact your system administrator.", 1); return 0; } if (!instantiated) { - Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1, _exitSignal, true); + Exit(Core.Constants.AppName + " is already running! Exiting the application.", 1); } Logger.LogInfo($"Launching {Core.Constants.AppName}..."); @@ -129,18 +131,26 @@ private static int Main(string[] args) return rootCommand.InvokeAsync(args).Result; } + private static void AwakeUnhandledExceptionCatcher(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception exception) + { + Logger.LogError(exception.ToString()); + Logger.LogError(exception.StackTrace); + } + } + private static bool ExitHandler(ControlType ctrlType) { Logger.LogInfo($"Exited through handler with control type: {ctrlType}"); - Exit(Resources.AWAKE_EXIT_MESSAGE, Environment.ExitCode, _exitSignal); + Exit(Resources.AWAKE_EXIT_MESSAGE, Environment.ExitCode); return false; } - private static void Exit(string message, int exitCode, ManualResetEvent exitSignal, bool force = false) + private static void Exit(string message, int exitCode) { Logger.LogInfo(message); - - Manager.CompleteExit(exitCode, exitSignal, force); + Manager.CompleteExit(exitCode); } private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid, string expireAt) @@ -169,6 +179,15 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, // Start the monitor thread that will be used to track the current state. Manager.StartMonitor(); + TrayHelper.InitializeTray(Core.Constants.FullAppName, _defaultAwakeIcon); + + var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent()); + new Thread(() => + { + WaitHandle.WaitAny([eventHandle]); + Exit(Resources.AWAKE_EXIT_SIGNAL_MESSAGE, 0); + }).Start(); + if (usePtConfig) { // Configuration file is used, therefore we disregard any other command-line parameter @@ -177,17 +196,6 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, try { - var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent()); - new Thread(() => - { - if (WaitHandle.WaitAny([_exitSignal, eventHandle]) == 1) - { - Exit(Resources.AWAKE_EXIT_SIGNAL_MESSAGE, 0, _exitSignal, true); - } - }).Start(); - - TrayHelper.InitializeTray(Core.Constants.FullAppName, new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/awake.ico")), _exitSignal); - string? settingsPath = _settingsUtils!.GetSettingsFilePath(Core.Constants.AppName); Logger.LogInfo($"Reading configuration file: {settingsPath}"); @@ -245,11 +253,9 @@ private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, RunnerHelper.WaitForPowerToysRunner(pid, () => { Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}."); - Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0, _exitSignal, true); + Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0); }); } - - _exitSignal.WaitOne(); } private static void ScaffoldConfiguration(string settingsPath) diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs index 2a8ac0a8cfa..0cdaf23605c 100644 --- a/src/modules/awake/Awake/Properties/Resources.Designer.cs +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -195,6 +195,42 @@ internal static string AWAKE_KEEP_UNTIL_EXPIRATION { } } + /// + /// Looks up a localized string similar to d. + /// + internal static string AWAKE_LABEL_DAYS { + get { + return ResourceManager.GetString("AWAKE_LABEL_DAYS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to h. + /// + internal static string AWAKE_LABEL_HOURS { + get { + return ResourceManager.GetString("AWAKE_LABEL_HOURS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to m. + /// + internal static string AWAKE_LABEL_MINUTES { + get { + return ResourceManager.GetString("AWAKE_LABEL_MINUTES", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to s. + /// + internal static string AWAKE_LABEL_SECONDS { + get { + return ResourceManager.GetString("AWAKE_LABEL_SECONDS", resourceCulture); + } + } + /// /// Looks up a localized string similar to {0} minutes. /// @@ -241,7 +277,7 @@ internal static string AWAKE_TRAY_TEXT_OFF { } /// - /// Looks up a localized string similar to Timed. + /// Looks up a localized string similar to Interval. /// internal static string AWAKE_TRAY_TEXT_TIMED { get { diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx index abb0f8ca36b..7ef19501287 100644 --- a/src/modules/awake/Awake/Properties/Resources.resx +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -187,6 +187,22 @@ Passive - Timed + Interval + + + d + Used to display number of days in the system tray tooltip. + + + h + Used to display number of hours in the system tray tooltip. + + + m + Used to display number of minutes in the system tray tooltip. + + + s + Used to display number of seconds in the system tray tooltip. \ No newline at end of file diff --git a/src/modules/awake/Awake/app.manifest b/src/modules/awake/Awake/app.manifest new file mode 100644 index 00000000000..a3d1e52638e --- /dev/null +++ b/src/modules/awake/Awake/app.manifest @@ -0,0 +1,8 @@ + + + + + true + + + \ No newline at end of file