mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-24 04:12:32 +08:00
[Awake]Refactor and update version - DAISY023_04102024
(#32378)
Improves the following: - Consolidates different code paths for easier maintenance. - Removes the dependency on Windows Forms and creates the system tray icon and handling through native Win32 APIs (massive thank you to @BrianPeek for helping write the window creation logic and diagnosing threading issues). - Changing modes in Awake now triggers icon changes in the tray (#11996). Massive thank you to @niels9001 for creating the icons. Fixes the following: - When in the UI and you select `0` as hours and `0` as minutes in `TIMED` awake mode, the UI becomes non-responsive whenever you try to get back to timed after it rolls back to `PASSIVE`. (#33630) - Adds the option to keep track of Awake state through tray tooltip. (#12714) --------- Co-authored-by: Clint Rutkas <clint@rutkas.com> Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
parent
63625a1cee
commit
1be3b6c087
31
.github/actions/spell-check/expect.txt
vendored
31
.github/actions/spell-check/expect.txt
vendored
@ -221,7 +221,6 @@ comdlg
|
||||
comexp
|
||||
cominterop
|
||||
commandline
|
||||
COMMANDTITLE
|
||||
commctrl
|
||||
commdlg
|
||||
compmgmt
|
||||
@ -430,8 +429,8 @@ ENDSESSION
|
||||
ENTERSIZEMOVE
|
||||
ENU
|
||||
EOAC
|
||||
epu
|
||||
EPO
|
||||
epu
|
||||
ERASEBKGND
|
||||
EREOF
|
||||
EResize
|
||||
@ -486,7 +485,6 @@ FILEFLAGSMASK
|
||||
FILELOCKSMITH
|
||||
FILELOCKSMITHCONTEXTMENU
|
||||
FILELOCKSMITHEXT
|
||||
FILELOCKSMITHLIB
|
||||
FILELOCKSMITHLIBINTEROP
|
||||
FILEMUSTEXIST
|
||||
FILEOP
|
||||
@ -502,6 +500,7 @@ findfast
|
||||
FIXEDFILEINFO
|
||||
flac
|
||||
flyouts
|
||||
FMask
|
||||
FOF
|
||||
FOFX
|
||||
FOLDERID
|
||||
@ -523,7 +522,6 @@ GCLP
|
||||
gdi
|
||||
gdiplus
|
||||
GDISCALED
|
||||
gdnbaselines
|
||||
GEmoji
|
||||
GETCLIENTAREAANIMATION
|
||||
GETDESKWALLPAPER
|
||||
@ -543,7 +541,6 @@ gpedit
|
||||
gpo
|
||||
GPOCA
|
||||
gpp
|
||||
GPT
|
||||
gpu
|
||||
GSM
|
||||
gtm
|
||||
@ -703,7 +700,6 @@ INSTALLSTARTMENUSHORTCUT
|
||||
INSTALLSTATE
|
||||
Inste
|
||||
Intelli
|
||||
interactable
|
||||
Interlop
|
||||
INTRESOURCE
|
||||
INVALIDARG
|
||||
@ -732,7 +728,6 @@ IWeb
|
||||
IWIC
|
||||
iwr
|
||||
IYUV
|
||||
JArray
|
||||
jfi
|
||||
jfif
|
||||
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
|
||||
@ -764,7 +759,6 @@ KILLFOCUS
|
||||
killrunner
|
||||
kmph
|
||||
Knownfolders
|
||||
ksh
|
||||
KSPROPERTY
|
||||
Kybd
|
||||
languagesjson
|
||||
@ -800,7 +794,6 @@ LOADFROMFILE
|
||||
LOBYTE
|
||||
LOCALDISPLAY
|
||||
LOCALPACKAGE
|
||||
localport
|
||||
LOCALSYSTEM
|
||||
LOCATIONCHANGE
|
||||
LOGFONT
|
||||
@ -814,6 +807,7 @@ LOWORD
|
||||
lparam
|
||||
LPBITMAPINFOHEADER
|
||||
LPCITEMIDLIST
|
||||
lpcmi
|
||||
LPCMINVOKECOMMANDINFO
|
||||
LPCREATESTRUCT
|
||||
LPCRECT
|
||||
@ -838,6 +832,7 @@ lptpm
|
||||
LPTR
|
||||
LPTSTR
|
||||
LPW
|
||||
lpwcx
|
||||
lpwndpl
|
||||
LReader
|
||||
LRESULT
|
||||
@ -855,10 +850,10 @@ lwin
|
||||
LZero
|
||||
majortype
|
||||
makecab
|
||||
MAKELANGID
|
||||
MAKEINTRESOURCE
|
||||
MAKEINTRESOURCEA
|
||||
MAKEINTRESOURCEW
|
||||
MAKELANGID
|
||||
makepri
|
||||
manifestdependency
|
||||
MAPPEDTOSAMEKEY
|
||||
@ -878,7 +873,6 @@ mdwn
|
||||
MEDIASUBTYPE
|
||||
mediatype
|
||||
mef
|
||||
MENUBREAK
|
||||
MENUITEMINFO
|
||||
MENUITEMINFOW
|
||||
MERGECOPY
|
||||
@ -1041,6 +1035,7 @@ NOSEARCH
|
||||
NOSENDCHANGING
|
||||
NOSIZE
|
||||
NOTIFICATIONSDLL
|
||||
NOTIFYICONDATA
|
||||
NOTIFYICONDATAW
|
||||
NOTIMPL
|
||||
notlike
|
||||
@ -1064,7 +1059,6 @@ numberbox
|
||||
nwc
|
||||
Objbase
|
||||
objidl
|
||||
occurrence
|
||||
ocr
|
||||
Ocrsettings
|
||||
odbccp
|
||||
@ -1078,7 +1072,6 @@ oldtheme
|
||||
oleaut
|
||||
OLECHAR
|
||||
onebranch
|
||||
OOBEPT
|
||||
opencode
|
||||
OPENFILENAME
|
||||
opensource
|
||||
@ -1102,7 +1095,6 @@ OVERLAPPEDWINDOW
|
||||
overlaywindow
|
||||
Oversampling
|
||||
OWNDC
|
||||
OWNERDRAW
|
||||
Packagemanager
|
||||
PACL
|
||||
PAINTSTRUCT
|
||||
@ -1116,7 +1108,6 @@ PARTIALCONFIRMATIONDIALOGTITLE
|
||||
PATCOPY
|
||||
pathcch
|
||||
PATHMUSTEXIST
|
||||
Pathto
|
||||
PATINVERT
|
||||
PATPAINT
|
||||
PAUDIO
|
||||
@ -1165,6 +1156,7 @@ ploca
|
||||
plocm
|
||||
pluginsmodel
|
||||
PMSIHANDLE
|
||||
pnid
|
||||
Pnp
|
||||
Popups
|
||||
POPUPWINDOW
|
||||
@ -1257,8 +1249,6 @@ QUERYENDSESSION
|
||||
QUERYOPEN
|
||||
QUEUESYNC
|
||||
QUNS
|
||||
qwertyuiopasdfghjklzxcvbnm
|
||||
qwrtyuiopsghjklzxvnm
|
||||
raf
|
||||
RAII
|
||||
RAlt
|
||||
@ -1275,7 +1265,6 @@ RECTDESTINATION
|
||||
rectp
|
||||
RECTSOURCE
|
||||
recyclebin
|
||||
redirectedfrom
|
||||
Redist
|
||||
redistributable
|
||||
reencode
|
||||
@ -1357,8 +1346,6 @@ runas
|
||||
rundll
|
||||
rungameid
|
||||
RUNLEVEL
|
||||
runsettings
|
||||
runspace
|
||||
runtimeclass
|
||||
runtimeobject
|
||||
runtimepack
|
||||
@ -1592,7 +1579,6 @@ TDevice
|
||||
telem
|
||||
telephon
|
||||
templatenamespace
|
||||
testhost
|
||||
testprocess
|
||||
TEXCOORD
|
||||
TEXTEXTRACTOR
|
||||
@ -1617,7 +1603,6 @@ tlb
|
||||
tlbimp
|
||||
TMPVAR
|
||||
TNP
|
||||
toggleswitch
|
||||
Toolhelp
|
||||
toolkitconverters
|
||||
Toolset
|
||||
@ -1647,9 +1632,11 @@ TYPESHORTCUT
|
||||
UAC
|
||||
UAL
|
||||
uap
|
||||
UCallback
|
||||
udit
|
||||
uefi
|
||||
uesc
|
||||
UFlags
|
||||
UHash
|
||||
UIA
|
||||
UIEx
|
||||
|
@ -85,7 +85,7 @@
|
||||
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
|
||||
|
||||
<!-- Change this to bust the cache -->
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202407100737</MSBuildCacheCacheUniverse>
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202407200737</MSBuildCacheCacheUniverse>
|
||||
|
||||
<!--
|
||||
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.
|
||||
|
@ -79,7 +79,7 @@
|
||||
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />
|
||||
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.0-preview.9" />
|
||||
<PackageVersion Include="System.Reactive" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Runtime.Caching" Version="8.0.0" />
|
||||
<!-- Package System.Security.Cryptography.ProtectedData added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Data.OleDb but the 8.0.1 version wasn't published to nuget. -->
|
||||
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||
|
@ -1359,7 +1359,7 @@ EXHIBIT A -Mozilla Public License.
|
||||
- System.IO.Abstractions 17.2.3
|
||||
- System.IO.Abstractions.TestingHelpers 17.2.3
|
||||
- System.Management 8.0.0
|
||||
- System.Reactive 6.0.0-preview.9
|
||||
- System.Reactive 6.0.1
|
||||
- System.Runtime.Caching 8.0.0
|
||||
- System.Security.Cryptography.ProtectedData 8.0.0
|
||||
- System.ServiceProcess.ServiceController 8.0.0
|
||||
|
@ -1,21 +1,34 @@
|
||||
---
|
||||
last-update: 3-20-2022
|
||||
last-update: 7-16-2024
|
||||
---
|
||||
|
||||
# PowerToys Awake Changelog
|
||||
|
||||
## Builds
|
||||
|
||||
The build ID can be found in `Program.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
|
||||
The build ID can be found in `Core\Constants.cs` in the `BuildId` variable - it is a unique identifier for the current builds that allows better diagnostics (we can look up the build ID from the logs) and offers a way to triage Awake-specific issues faster independent of the PowerToys version. The build ID does not carry any significance beyond that within the PowerToys code base.
|
||||
|
||||
The build ID moniker is made up of two components - a reference to a [Halo](https://en.wikipedia.org/wiki/Halo_(franchise)) character, and the date when the work on the specific build started in the format of `MMDDYYYY`.
|
||||
|
||||
| Build ID | Build Date |
|
||||
|:----------------------------------------------------------|:-----------------|
|
||||
| [`DAISY023_04102024`](#DAISY023_04102024-april-10-2024) | April 10, 2024 |
|
||||
| [`ATRIOX_04132023`](#ATRIOX_04132023-april-13-2023) | April 13, 2023 |
|
||||
| [`LIBRARIAN_03202022`](#librarian_03202022-march-20-2022) | March 20, 2022 |
|
||||
| `ARBITER_01312022` | January 31, 2022 |
|
||||
|
||||
### `DAISY023_04102024` (April 10, 2024)
|
||||
|
||||
>[!NOTE]
|
||||
>See pull request: [Awake Update - `DAISY023_04102024`](https://github.com/microsoft/PowerToys/pull/32378)
|
||||
|
||||
- [#33630](https://github.com/microsoft/PowerToys/issues/33630) When in the UI and you select `0` as hours and `0` as minutes in `TIMED` awake mode, the UI becomes non-responsive whenever you try to get back to timed after it rolls back to `PASSIVE`.
|
||||
- [#12714](https://github.com/microsoft/PowerToys/issues/12714) Adds the option to keep track of Awake state through tray tooltip.
|
||||
- [#11996](https://github.com/microsoft/PowerToys/issues/11996) Adds custom icons support for mode changes in Awake.
|
||||
- Removes the dependency on `System.Windows.Forms` and instead uses native Windows APIs to create the tray icon.
|
||||
- Removes redundant/unused code that impacted application performance.
|
||||
- Updates dependent packages to their latest versions (`Microsoft.Windows.CsWinRT` and `System.Reactive`).
|
||||
|
||||
### `ATRIOX_04132023` (April 13, 2023)
|
||||
|
||||
- Moves from using `Task.Run` to spin up threads to actually using a blocking queue that properly sets thread parameters on the same thread.
|
||||
|
BIN
src/modules/awake/Awake/Assets/Awake/disabled.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/disabled.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
src/modules/awake/Awake/Assets/Awake/expirable.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/expirable.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
src/modules/awake/Awake/Assets/Awake/indefinite.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/indefinite.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
src/modules/awake/Awake/Assets/Awake/normal.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/normal.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
src/modules/awake/Awake/Assets/Awake/scheduled.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/scheduled.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
src/modules/awake/Awake/Assets/Awake/timed.ico
Normal file
BIN
src/modules/awake/Awake/Assets/Awake/timed.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
@ -10,7 +10,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWindowsForms>False</UseWindowsForms>
|
||||
<!--Per documentation: https://learn.microsoft.com/dotnet/core/compatibility/windows-forms/5.0/automatically-infer-winexe-output-type#outputtype-set-to-winexe-for-wpf-and-winforms-apps -->
|
||||
<DisableWinExeOutputInference>true</DisableWinExeOutputInference>
|
||||
<AssemblyName>PowerToys.Awake</AssemblyName>
|
||||
@ -59,6 +59,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Awake\Awake.ico" />
|
||||
<None Remove="Assets\Awake\disabled.ico" />
|
||||
<None Remove="Assets\Awake\expirable.ico" />
|
||||
<None Remove="Assets\Awake\indefinite.ico" />
|
||||
<None Remove="Assets\Awake\normal.ico" />
|
||||
<None Remove="Assets\Awake\scheduled.ico" />
|
||||
<None Remove="Assets\Awake\timed.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -90,6 +96,24 @@
|
||||
<Content Include="Assets\Awake\Awake.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\disabled.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\expirable.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\indefinite.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\normal.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\scheduled.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Assets\Awake\timed.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -8,7 +8,15 @@ namespace Awake.Core
|
||||
{
|
||||
internal const string AppName = "Awake";
|
||||
internal const string FullAppName = "PowerToys " + AppName;
|
||||
internal const string TrayWindowId = "WindowsForms10.Window.0.app.0.";
|
||||
internal const string TrayWindowId = "Awake.MessageWindow";
|
||||
internal const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
|
||||
|
||||
// PowerToys Awake build code name. Used for exact logging
|
||||
// that does not map to PowerToys broad version schema to pinpoint
|
||||
// internal issues easier.
|
||||
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
|
||||
// is representative of the date when the last change was made before
|
||||
// the pull request is issued.
|
||||
internal const string BuildId = "DAISY023_04102024";
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ namespace Awake.Core
|
||||
public static void AddRange<T>(this ICollection<T> target, IEnumerable<T> source)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(target);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
foreach (var element in source)
|
||||
|
@ -5,7 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reactive.Linq;
|
||||
@ -30,25 +30,29 @@ namespace Awake.Core
|
||||
/// </summary>
|
||||
public class Manager
|
||||
{
|
||||
private static readonly CompositeFormat AwakeMinutes = System.Text.CompositeFormat.Parse(Properties.Resources.AWAKE_MINUTES);
|
||||
private static readonly CompositeFormat AwakeHours = System.Text.CompositeFormat.Parse(Properties.Resources.AWAKE_HOURS);
|
||||
private static bool _isUsingPowerToysConfig;
|
||||
|
||||
private static BlockingCollection<ExecutionState> _stateQueue;
|
||||
internal static bool IsUsingPowerToysConfig { get => _isUsingPowerToysConfig; set => _isUsingPowerToysConfig = value; }
|
||||
|
||||
private static readonly CompositeFormat AwakeMinutes = CompositeFormat.Parse(Resources.AWAKE_MINUTES);
|
||||
private static readonly CompositeFormat AwakeHours = CompositeFormat.Parse(Resources.AWAKE_HOURS);
|
||||
|
||||
private static readonly BlockingCollection<ExecutionState> _stateQueue;
|
||||
|
||||
private static CancellationTokenSource _tokenSource;
|
||||
|
||||
private static SettingsUtils? _moduleSettings;
|
||||
|
||||
private static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
|
||||
internal static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
|
||||
|
||||
static Manager()
|
||||
{
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
_stateQueue = new BlockingCollection<ExecutionState>();
|
||||
_stateQueue = [];
|
||||
ModuleSettings = new SettingsUtils();
|
||||
}
|
||||
|
||||
public static void StartMonitor()
|
||||
internal static void StartMonitor()
|
||||
{
|
||||
Thread monitorThread = new(() =>
|
||||
{
|
||||
@ -70,7 +74,7 @@ namespace Awake.Core
|
||||
Bridge.SetConsoleCtrlHandler(handler, addHandler);
|
||||
}
|
||||
|
||||
public static void AllocateConsole()
|
||||
internal static void AllocateConsole()
|
||||
{
|
||||
Bridge.AllocConsole();
|
||||
|
||||
@ -103,17 +107,12 @@ namespace Awake.Core
|
||||
|
||||
private static ExecutionState ComputeAwakeState(bool keepDisplayOn)
|
||||
{
|
||||
if (keepDisplayOn)
|
||||
{
|
||||
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS;
|
||||
}
|
||||
return keepDisplayOn
|
||||
? ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_DISPLAY_REQUIRED | ExecutionState.ES_CONTINUOUS
|
||||
: ExecutionState.ES_SYSTEM_REQUIRED | ExecutionState.ES_CONTINUOUS;
|
||||
}
|
||||
|
||||
public static void CancelExistingThread()
|
||||
internal static void CancelExistingThread()
|
||||
{
|
||||
Logger.LogInfo($"Attempting to ensure that the thread is properly cleaned up...");
|
||||
|
||||
@ -128,81 +127,156 @@ namespace Awake.Core
|
||||
Logger.LogInfo("Instantiating of new token source and thread token completed.");
|
||||
}
|
||||
|
||||
public static void SetIndefiniteKeepAwake(bool keepDisplayOn = false)
|
||||
internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent());
|
||||
|
||||
CancelExistingThread();
|
||||
|
||||
_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);
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
|
||||
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.INDEFINITE ||
|
||||
currentSettings.Properties.KeepDisplayOn != keepDisplayOn;
|
||||
|
||||
if (settingsChanged)
|
||||
{
|
||||
currentSettings.Properties.Mode = AwakeMode.INDEFINITE;
|
||||
currentSettings.Properties.KeepDisplayOn = keepDisplayOn;
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetNoKeepAwake()
|
||||
internal static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent());
|
||||
Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {keepDisplayOn}.");
|
||||
|
||||
CancelExistingThread();
|
||||
}
|
||||
|
||||
public static void SetExpirableKeepAwake(DateTimeOffset expireAt, bool keepDisplayOn = true)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeExpirableKeepAwakeEvent());
|
||||
|
||||
CancelExistingThread();
|
||||
|
||||
if (expireAt > DateTime.Now && expireAt != null)
|
||||
if (expireAt > DateTimeOffset.Now)
|
||||
{
|
||||
Logger.LogInfo($"Starting expirable log for {expireAt}");
|
||||
_stateQueue.Add(ComputeAwakeState(keepDisplayOn));
|
||||
|
||||
Observable.Timer(expireAt).Subscribe(
|
||||
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, $"{Constants.FullAppName} [{Resources.AWAKE_TRAY_TEXT_EXPIRATION}]", new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets/Awake/expirable.ico")), TrayIconAction.Update);
|
||||
|
||||
Observable.Timer(expireAt - DateTimeOffset.Now).Subscribe(
|
||||
_ =>
|
||||
{
|
||||
Logger.LogInfo($"Completed expirable keep-awake.");
|
||||
CancelExistingThread();
|
||||
|
||||
SetPassiveKeepAwakeMode(Constants.AppName);
|
||||
SetPassiveKeepAwake();
|
||||
},
|
||||
_tokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The target date is not in the future.
|
||||
Logger.LogError("The specified target date and time is not in the future.");
|
||||
Logger.LogError($"Current time: {DateTime.Now}\tTarget time: {expireAt}");
|
||||
Logger.LogError($"Current time: {DateTimeOffset.Now}\tTarget time: {expireAt}");
|
||||
}
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
|
||||
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.EXPIRABLE ||
|
||||
currentSettings.Properties.ExpirationDateTime != expireAt ||
|
||||
currentSettings.Properties.KeepDisplayOn != keepDisplayOn;
|
||||
|
||||
if (settingsChanged)
|
||||
{
|
||||
currentSettings.Properties.Mode = AwakeMode.EXPIRABLE;
|
||||
currentSettings.Properties.KeepDisplayOn = keepDisplayOn;
|
||||
currentSettings.Properties.ExpirationDateTime = expireAt;
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to handle indefinite keep awake command: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true)
|
||||
internal static void SetTimedKeepAwake(uint seconds, bool keepDisplayOn = true)
|
||||
{
|
||||
Logger.LogInfo($"Timed keep-awake. Expected runtime: {seconds} seconds with display on setting set to {keepDisplayOn}.");
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeTimedKeepAwakeEvent());
|
||||
|
||||
CancelExistingThread();
|
||||
|
||||
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);
|
||||
|
||||
Observable.Timer(TimeSpan.FromSeconds(seconds)).Subscribe(
|
||||
_ =>
|
||||
{
|
||||
Logger.LogInfo($"Completed timed thread.");
|
||||
CancelExistingThread();
|
||||
|
||||
SetPassiveKeepAwakeMode(Constants.AppName);
|
||||
SetPassiveKeepAwake();
|
||||
},
|
||||
_tokenSource.Token);
|
||||
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
|
||||
var timeSpan = TimeSpan.FromSeconds(seconds);
|
||||
var settingsChanged = currentSettings.Properties.Mode != AwakeMode.TIMED ||
|
||||
currentSettings.Properties.IntervalHours != (uint)timeSpan.Hours ||
|
||||
currentSettings.Properties.IntervalMinutes != (uint)timeSpan.Minutes;
|
||||
|
||||
if (settingsChanged)
|
||||
{
|
||||
currentSettings.Properties.Mode = AwakeMode.TIMED;
|
||||
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
|
||||
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to handle timed keep awake command: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a clean exit from Awake.
|
||||
/// </summary>
|
||||
/// <param name="exitCode">Exit code to exit with.</param>
|
||||
/// <param name="exitSignal">Exit signal tracking the state.</param>
|
||||
/// <param name="force">Determines whether to force exit and post a quitting message.</param>
|
||||
internal static void CompleteExit(int exitCode, ManualResetEvent? exitSignal, bool force = false)
|
||||
{
|
||||
SetNoKeepAwake();
|
||||
SetPassiveKeepAwake(updateSettings: false);
|
||||
|
||||
IntPtr windowHandle = GetHiddenWindow();
|
||||
|
||||
if (windowHandle != IntPtr.Zero)
|
||||
if (TrayHelper.HiddenWindowHandle != IntPtr.Zero)
|
||||
{
|
||||
Bridge.SendMessage(windowHandle, Native.Constants.WM_CLOSE, 0, 0);
|
||||
// Delete the icon.
|
||||
TrayHelper.SetShellIcon(TrayHelper.HiddenWindowHandle, string.Empty, null, TrayIconAction.Delete);
|
||||
|
||||
// Close the message window that we used for the tray.
|
||||
Bridge.SendMessage(TrayHelper.HiddenWindowHandle, Native.Constants.WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
if (force)
|
||||
@ -213,7 +287,7 @@ namespace Awake.Core
|
||||
try
|
||||
{
|
||||
exitSignal?.Set();
|
||||
Bridge.DestroyWindow(windowHandle);
|
||||
Bridge.DestroyWindow(TrayHelper.HiddenWindowHandle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -221,7 +295,11 @@ namespace Awake.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetOperatingSystemBuild()
|
||||
/// <summary>
|
||||
/// Gets the operating system for logging purposes.
|
||||
/// </summary>
|
||||
/// <returns>Returns the string representing the current OS build.</returns>
|
||||
internal static string GetOperatingSystemBuild()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -245,83 +323,71 @@ 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.")]
|
||||
internal static IEnumerable<IntPtr> EnumerateWindowsForProcess(int processId)
|
||||
/// <summary>
|
||||
/// Generates the default system tray options in situations where no custom options are provided.
|
||||
/// </summary>
|
||||
/// <returns>Returns a dictionary of default Awake timed interval options.</returns>
|
||||
internal static Dictionary<string, int> GetDefaultTrayOptions()
|
||||
{
|
||||
var handles = new List<IntPtr>();
|
||||
var hCurrentWnd = IntPtr.Zero;
|
||||
|
||||
do
|
||||
{
|
||||
hCurrentWnd = Bridge.FindWindowEx(IntPtr.Zero, hCurrentWnd, null as string, null);
|
||||
Bridge.GetWindowThreadProcessId(hCurrentWnd, out uint targetProcessId);
|
||||
|
||||
if (targetProcessId == processId)
|
||||
{
|
||||
handles.Add(hCurrentWnd);
|
||||
}
|
||||
}
|
||||
while (hCurrentWnd != IntPtr.Zero);
|
||||
|
||||
return handles;
|
||||
}
|
||||
|
||||
[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "In this context, the string is only converted to a hex value.")]
|
||||
internal static IntPtr GetHiddenWindow()
|
||||
{
|
||||
IEnumerable<IntPtr> windowHandles = EnumerateWindowsForProcess(Environment.ProcessId);
|
||||
var domain = AppDomain.CurrentDomain.GetHashCode().ToString("x");
|
||||
string targetClass = $"{Constants.TrayWindowId}{domain}";
|
||||
|
||||
foreach (var handle in windowHandles)
|
||||
{
|
||||
StringBuilder className = new(256);
|
||||
int classQueryResult = Bridge.GetClassName(handle, className, className.Capacity);
|
||||
if (classQueryResult != 0 && className.ToString().StartsWith(targetClass, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
public static Dictionary<string, int> GetDefaultTrayOptions()
|
||||
{
|
||||
Dictionary<string, int> optionsList = new Dictionary<string, int>
|
||||
Dictionary<string, int> optionsList = new()
|
||||
{
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeMinutes, 30), 1800 },
|
||||
{ Resources.AWAKE_1_HOUR, 3600 },
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 1), 3600 },
|
||||
{ string.Format(CultureInfo.InvariantCulture, AwakeHours, 2), 7200 },
|
||||
};
|
||||
return optionsList;
|
||||
}
|
||||
|
||||
public static void SetPassiveKeepAwakeMode(string moduleName)
|
||||
/// <summary>
|
||||
/// Resets the computer to standard power settings.
|
||||
/// </summary>
|
||||
/// <param name="updateSettings">In certain cases, such as exits, we want to make sure that settings are not reset for the passive mode but rather retained based on previous execution. Default is to save settings, but otherwise it can be overridden.</param>
|
||||
internal static void SetPassiveKeepAwake(bool updateSettings = true)
|
||||
{
|
||||
AwakeSettings currentSettings;
|
||||
Logger.LogInfo($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeNoKeepAwakeEvent());
|
||||
|
||||
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);
|
||||
|
||||
if (IsUsingPowerToysConfig && updateSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed to reset Awake mode GetSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
currentSettings = new AwakeSettings();
|
||||
}
|
||||
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
|
||||
|
||||
if (currentSettings.Properties.Mode != AwakeMode.PASSIVE)
|
||||
{
|
||||
currentSettings.Properties.Mode = AwakeMode.PASSIVE;
|
||||
|
||||
try
|
||||
{
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed to reset Awake mode SaveSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
Logger.LogError($"Failed to reset Awake mode: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the display settings.
|
||||
/// </summary>
|
||||
internal static void SetDisplay()
|
||||
{
|
||||
if (IsUsingPowerToysConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName) ?? new AwakeSettings();
|
||||
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), Constants.AppName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to handle display setting command: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
src/modules/awake/Awake/Core/Models/MSG.cs
Normal file
18
src/modules/awake/Awake/Core/Models/MSG.cs
Normal file
@ -0,0 +1,18 @@
|
||||
// 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
|
||||
{
|
||||
internal struct Msg
|
||||
{
|
||||
public IntPtr HWnd;
|
||||
public uint Message;
|
||||
public IntPtr WParam;
|
||||
public IntPtr LParam;
|
||||
public uint Time;
|
||||
public Point Pt;
|
||||
}
|
||||
}
|
21
src/modules/awake/Awake/Core/Models/MenuInfo.cs
Normal file
21
src/modules/awake/Awake/Core/Models/MenuInfo.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Awake.Core.Models
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct MenuInfo
|
||||
{
|
||||
public uint CbSize; // Size of the structure, in bytes
|
||||
public uint FMask; // Specifies which members of the structure are valid
|
||||
public uint DwStyle; // Style of the menu
|
||||
public uint CyMax; // Maximum height of the menu, in pixels
|
||||
public IntPtr HbrBack; // Handle to the brush used for the menu's background
|
||||
public uint DwContextHelpID; // Context help ID
|
||||
public IntPtr DwMenuData; // Pointer to the menu's user data
|
||||
}
|
||||
}
|
22
src/modules/awake/Awake/Core/Models/NOTIFYICONDATA.cs
Normal file
22
src/modules/awake/Awake/Core/Models/NOTIFYICONDATA.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Awake.Core.Models
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NotifyIconData
|
||||
{
|
||||
public int CbSize;
|
||||
public IntPtr HWnd;
|
||||
public int UId;
|
||||
public int UFlags;
|
||||
public int UCallbackMessage;
|
||||
public IntPtr HIcon;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string SzTip;
|
||||
}
|
||||
}
|
15
src/modules/awake/Awake/Core/Models/POINT.cs
Normal file
15
src/modules/awake/Awake/Core/Models/POINT.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// 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
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Awake.Core.Models
|
||||
{
|
||||
internal sealed class SingleThreadSynchronizationContext : SynchronizationContext
|
||||
{
|
||||
private readonly Queue<Tuple<SendOrPostCallback, object>> queue =
|
||||
new();
|
||||
|
||||
#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
|
||||
public override void Post(SendOrPostCallback d, object state)
|
||||
#pragma warning restore CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes).
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
queue.Enqueue(Tuple.Create(d, state));
|
||||
Monitor.Pulse(queue);
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginMessageLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Tuple<SendOrPostCallback, object> work;
|
||||
lock (queue)
|
||||
{
|
||||
while (queue.Count == 0)
|
||||
{
|
||||
Monitor.Wait(queue);
|
||||
}
|
||||
|
||||
work = queue.Dequeue();
|
||||
}
|
||||
|
||||
if (work == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
work.Item1(work.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndMessageLoop()
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
queue.Enqueue(null); // Signal the end of the message loop
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
Monitor.Pulse(queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,11 +6,11 @@ namespace Awake.Core.Models
|
||||
{
|
||||
internal enum TrayCommands : uint
|
||||
{
|
||||
TC_DISPLAY_SETTING = Native.Constants.WM_USER + 1,
|
||||
TC_MODE_PASSIVE = Native.Constants.WM_USER + 2,
|
||||
TC_MODE_INDEFINITE = Native.Constants.WM_USER + 3,
|
||||
TC_MODE_EXPIRABLE = Native.Constants.WM_USER + 4,
|
||||
TC_EXIT = Native.Constants.WM_USER + 100,
|
||||
TC_TIME = Native.Constants.WM_USER + 101,
|
||||
TC_DISPLAY_SETTING = Native.Constants.WM_USER + 0x2,
|
||||
TC_MODE_PASSIVE = Native.Constants.WM_USER + 0x3,
|
||||
TC_MODE_INDEFINITE = Native.Constants.WM_USER + 0x4,
|
||||
TC_MODE_EXPIRABLE = Native.Constants.WM_USER + 0x5,
|
||||
TC_EXIT = Native.Constants.WM_USER + 0x64,
|
||||
TC_TIME = Native.Constants.WM_USER + 0x65,
|
||||
}
|
||||
}
|
||||
|
13
src/modules/awake/Awake/Core/Models/TrayIconAction.cs
Normal file
13
src/modules/awake/Awake/Core/Models/TrayIconAction.cs
Normal file
@ -0,0 +1,13 @@
|
||||
// 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
|
||||
{
|
||||
internal enum TrayIconAction
|
||||
{
|
||||
Add,
|
||||
Update,
|
||||
Delete,
|
||||
}
|
||||
}
|
26
src/modules/awake/Awake/Core/Models/WNDCLASSEX.cs
Normal file
26
src/modules/awake/Awake/Core/Models/WNDCLASSEX.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Awake.Core.Models
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct WndClassEx
|
||||
{
|
||||
public uint CbSize;
|
||||
public uint Style;
|
||||
public IntPtr LpfnWndProc;
|
||||
public int CbClsExtra;
|
||||
public int CbWndExtra;
|
||||
public IntPtr HInstance;
|
||||
public IntPtr HIcon;
|
||||
public IntPtr HCursor;
|
||||
public IntPtr HbrBackground;
|
||||
public string LpszMenuName;
|
||||
public string LpszClassName;
|
||||
public IntPtr HIconSm;
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Awake.Core.Models;
|
||||
|
||||
namespace Awake.Core.Native
|
||||
{
|
||||
internal sealed class Bridge
|
||||
{
|
||||
internal delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = true)]
|
||||
internal delegate int WndProcDelegate(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("Powrprof.dll", SetLastError = true)]
|
||||
internal static extern bool GetPwrCapabilities(out SystemPowerCapabilities lpSystemPowerCapabilities);
|
||||
@ -30,9 +30,6 @@ namespace Awake.Core.Native
|
||||
[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,
|
||||
@ -50,25 +47,13 @@ namespace Awake.Core.Native
|
||||
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);
|
||||
public static extern bool TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hWnd, IntPtr lptpm);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, nuint wParam, nint lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DestroyMenu(IntPtr hMenu);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
@ -76,5 +61,46 @@ namespace Awake.Core.Native
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern void PostQuitMessage(int nExitCode);
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
internal static extern bool Shell_NotifyIcon(int dwMessage, ref NotifyIconData pnid);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern bool TranslateMessage(ref Msg lpMsg);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern IntPtr DispatchMessage(ref Msg lpMsg);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern IntPtr RegisterClassEx(ref WndClassEx lpwcx);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
internal static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern int DefWindowProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetCursorPos(out Point lpPoint);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern bool UpdateWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SetMenuInfo(IntPtr hMenu, ref MenuInfo lpcmi);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
}
|
||||
}
|
||||
|
@ -7,26 +7,46 @@ namespace Awake.Core.Native
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 API convention.")]
|
||||
internal sealed class Constants
|
||||
{
|
||||
internal const uint WM_COMMAND = 0x111;
|
||||
internal const uint WM_USER = 0x400;
|
||||
internal const uint WM_GETTEXT = 0x000D;
|
||||
// Window Messages
|
||||
internal const uint WM_COMMAND = 0x0111;
|
||||
internal const uint WM_USER = 0x0400U;
|
||||
internal const uint WM_CLOSE = 0x0010;
|
||||
internal const int WM_DESTROY = 0x0002;
|
||||
internal const int WM_LBUTTONDOWN = 0x0201;
|
||||
internal const int WM_RBUTTONDOWN = 0x0204;
|
||||
|
||||
// Popup menu constants.
|
||||
// Menu Flags
|
||||
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;
|
||||
|
||||
internal const uint MF_ENABLED = 0x00000000;
|
||||
internal const uint MF_DISABLED = 0x00000002;
|
||||
|
||||
// Standard Handles
|
||||
internal const int STD_OUTPUT_HANDLE = -11;
|
||||
|
||||
// Generic Access Rights
|
||||
internal const uint GENERIC_WRITE = 0x40000000;
|
||||
internal const uint GENERIC_READ = 0x80000000;
|
||||
|
||||
// Notification Icons
|
||||
internal const int NIF_ICON = 0x00000002;
|
||||
internal const int NIF_MESSAGE = 0x00000001;
|
||||
internal const int NIF_TIP = 0x00000004;
|
||||
internal const int NIM_ADD = 0x00000000;
|
||||
internal const int NIM_DELETE = 0x00000002;
|
||||
internal const int NIM_MODIFY = 0x00000001;
|
||||
|
||||
// Track Popup Menu Flags
|
||||
internal const uint TPM_LEFT_ALIGN = 0x0000;
|
||||
internal const uint TPM_BOTTOMALIGN = 0x0020;
|
||||
internal const uint TPM_LEFT_BUTTON = 0x0000;
|
||||
|
||||
// Menu Item Info Flags
|
||||
internal const uint MNS_AUTO_DISMISS = 0x10000000;
|
||||
internal const uint MIM_STYLE = 0x00000010;
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Awake.Core.Models;
|
||||
using Awake.Core.Native;
|
||||
using Awake.Properties;
|
||||
@ -27,147 +27,348 @@ namespace Awake.Core
|
||||
/// </remarks>
|
||||
internal static class TrayHelper
|
||||
{
|
||||
private static NotifyIconData _notifyIconData;
|
||||
private static ManualResetEvent? _exitSignal;
|
||||
|
||||
private static IntPtr _trayMenu;
|
||||
|
||||
private static IntPtr TrayMenu { get => _trayMenu; set => _trayMenu = value; }
|
||||
|
||||
private static NotifyIcon TrayIcon { get; set; }
|
||||
private static IntPtr _hiddenWindowHandle;
|
||||
|
||||
internal static IntPtr HiddenWindowHandle { get => _hiddenWindowHandle; private set => _hiddenWindowHandle = value; }
|
||||
|
||||
static TrayHelper()
|
||||
{
|
||||
TrayIcon = new NotifyIcon();
|
||||
TrayMenu = IntPtr.Zero;
|
||||
HiddenWindowHandle = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal, ContextMenuStrip? contextMenu = null)
|
||||
public static void InitializeTray(string text, Icon icon, ManualResetEvent? exitSignal)
|
||||
{
|
||||
Task.Factory.StartNew(
|
||||
(tray) =>
|
||||
_exitSignal = exitSignal;
|
||||
|
||||
CreateHiddenWindow(icon, text);
|
||||
}
|
||||
|
||||
private static void ShowContextMenu(IntPtr hWnd)
|
||||
{
|
||||
Bridge.SetForegroundWindow(hWnd);
|
||||
|
||||
// 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);
|
||||
|
||||
Bridge.ScreenToClient(hWnd, ref cursorPos);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static void CreateHiddenWindow(Icon icon, string text)
|
||||
{
|
||||
IntPtr hWnd = IntPtr.Zero;
|
||||
|
||||
// Start the message loop asynchronously
|
||||
Task.Run(() =>
|
||||
{
|
||||
RunOnMainThread(() =>
|
||||
{
|
||||
WndClassEx wcex = new()
|
||||
{
|
||||
CbSize = (uint)Marshal.SizeOf(typeof(WndClassEx)),
|
||||
Style = 0,
|
||||
LpfnWndProc = Marshal.GetFunctionPointerForDelegate<Bridge.WndProcDelegate>(WndProc),
|
||||
CbClsExtra = 0,
|
||||
CbWndExtra = 0,
|
||||
HInstance = Marshal.GetHINSTANCE(typeof(Program).Module),
|
||||
HIcon = IntPtr.Zero,
|
||||
HCursor = IntPtr.Zero,
|
||||
HbrBackground = IntPtr.Zero,
|
||||
LpszMenuName = string.Empty,
|
||||
LpszClassName = Constants.TrayWindowId,
|
||||
HIconSm = IntPtr.Zero,
|
||||
};
|
||||
|
||||
Bridge.RegisterClassEx(ref wcex);
|
||||
|
||||
hWnd = Bridge.CreateWindowEx(
|
||||
0,
|
||||
Constants.TrayWindowId,
|
||||
text,
|
||||
0x00CF0000 | 0x00000001 | 0x00000008, // WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MINIMIZEBOX
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
unchecked(-3),
|
||||
IntPtr.Zero,
|
||||
Marshal.GetHINSTANCE(typeof(Program).Module),
|
||||
IntPtr.Zero);
|
||||
|
||||
if (hWnd == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(errorCode, "Failed to add tray icon. Error code: " + errorCode);
|
||||
}
|
||||
|
||||
// Keep this as a reference because we will need it when we update
|
||||
// the tray icon in the future.
|
||||
HiddenWindowHandle = hWnd;
|
||||
|
||||
Bridge.ShowWindow(hWnd, 0); // SW_HIDE
|
||||
Bridge.UpdateWindow(hWnd);
|
||||
|
||||
SetShellIcon(hWnd, text, icon);
|
||||
|
||||
RunMessageLoop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
internal static void SetShellIcon(IntPtr hWnd, string text, Icon? icon, TrayIconAction action = TrayIconAction.Add)
|
||||
{
|
||||
int message = Native.Constants.NIM_ADD;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case TrayIconAction.Update:
|
||||
message = Native.Constants.NIM_MODIFY;
|
||||
break;
|
||||
case TrayIconAction.Delete:
|
||||
message = Native.Constants.NIM_DELETE;
|
||||
break;
|
||||
case TrayIconAction.Add:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_notifyIconData = action == TrayIconAction.Add || action == TrayIconAction.Update
|
||||
? 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,
|
||||
SzTip = text,
|
||||
}
|
||||
: 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}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunMessageLoop()
|
||||
{
|
||||
while (Bridge.GetMessage(out Msg msg, IntPtr.Zero, 0, 0))
|
||||
{
|
||||
Bridge.TranslateMessage(ref msg);
|
||||
Bridge.DispatchMessage(ref msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static int WndProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case Native.Constants.WM_USER:
|
||||
if (lParam == (IntPtr)Native.Constants.WM_LBUTTONDOWN || lParam == (IntPtr)Native.Constants.WM_RBUTTONDOWN)
|
||||
{
|
||||
// Show the context menu associated with the tray icon
|
||||
ShowContextMenu(hWnd);
|
||||
}
|
||||
|
||||
break;
|
||||
case Native.Constants.WM_DESTROY:
|
||||
// Clean up resources when the window is destroyed
|
||||
Bridge.PostQuitMessage(0);
|
||||
break;
|
||||
case Native.Constants.WM_COMMAND:
|
||||
int trayCommandsSize = Enum.GetNames(typeof(TrayCommands)).Length;
|
||||
|
||||
long targetCommandIndex = wParam.ToInt64() & 0xFFFF;
|
||||
|
||||
switch (targetCommandIndex)
|
||||
{
|
||||
case (uint)TrayCommands.TC_EXIT:
|
||||
Manager.CompleteExit(0, _exitSignal, true);
|
||||
break;
|
||||
case (uint)TrayCommands.TC_DISPLAY_SETTING:
|
||||
Manager.SetDisplay();
|
||||
break;
|
||||
case (uint)TrayCommands.TC_MODE_INDEFINITE:
|
||||
Manager.SetIndefiniteKeepAwake();
|
||||
break;
|
||||
case (uint)TrayCommands.TC_MODE_PASSIVE:
|
||||
Manager.SetPassiveKeepAwake();
|
||||
break;
|
||||
default:
|
||||
if (targetCommandIndex >= trayCommandsSize)
|
||||
{
|
||||
AwakeSettings settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(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);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
// Let the default window procedure handle other messages
|
||||
return Bridge.DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
return Bridge.DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
internal static void RunOnMainThread(Action action)
|
||||
{
|
||||
var syncContext = new SingleThreadSynchronizationContext();
|
||||
SynchronizationContext.SetSynchronizationContext(syncContext);
|
||||
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
syncContext.Post(
|
||||
_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Setting up the tray.");
|
||||
if (tray != null)
|
||||
{
|
||||
((NotifyIcon)tray).Text = text;
|
||||
((NotifyIcon)tray).Icon = icon;
|
||||
((NotifyIcon)tray).ContextMenuStrip = contextMenu;
|
||||
((NotifyIcon)tray).Visible = true;
|
||||
((NotifyIcon)tray).MouseClick += TrayClickHandler;
|
||||
Application.AddMessageFilter(new TrayMessageFilter(exitSignal));
|
||||
Application.Run();
|
||||
Logger.LogInfo("Tray setup complete.");
|
||||
action();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"An error occurred initializing the tray. {ex.Message}");
|
||||
Logger.LogError($"{ex.StackTrace}");
|
||||
Console.WriteLine("Error: " + e.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
syncContext.EndMessageLoop();
|
||||
}
|
||||
},
|
||||
TrayIcon);
|
||||
null);
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
syncContext.BeginMessageLoop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Function used to construct the context menu in the tray natively.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We need to use the Windows API here instead of the common control exposed
|
||||
/// by NotifyIcon because the one that is built into the Windows Forms stack
|
||||
/// hasn't been updated in a while and is looking like Office XP. That introduces
|
||||
/// scalability and coloring changes on any OS past Windows XP.
|
||||
/// </remarks>
|
||||
/// <param name="sender">The sender that triggers the handler.</param>
|
||||
/// <param name="e">MouseEventArgs instance containing mouse click event information.</param>
|
||||
private static void TrayClickHandler(object? sender, MouseEventArgs e)
|
||||
{
|
||||
IntPtr windowHandle = Manager.GetHiddenWindow();
|
||||
|
||||
if (windowHandle != IntPtr.Zero)
|
||||
{
|
||||
Bridge.SetForegroundWindow(windowHandle);
|
||||
Bridge.TrackPopupMenuEx(TrayMenu, 0, Cursor.Position.X, Cursor.Position.Y, windowHandle, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetTray(string text, AwakeSettings settings, bool startedFromPowerToys)
|
||||
internal static void SetTray(AwakeSettings settings, bool startedFromPowerToys)
|
||||
{
|
||||
SetTray(
|
||||
text,
|
||||
settings.Properties.KeepDisplayOn,
|
||||
settings.Properties.Mode,
|
||||
settings.Properties.CustomTrayTimes,
|
||||
startedFromPowerToys);
|
||||
}
|
||||
|
||||
public static void SetTray(string text, bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts, bool startedFromPowerToys)
|
||||
public static void SetTray(bool keepDisplayOn, AwakeMode mode, Dictionary<string, int> trayTimeShortcuts, bool startedFromPowerToys)
|
||||
{
|
||||
if (TrayMenu != IntPtr.Zero)
|
||||
ClearExistingTrayMenu();
|
||||
CreateNewTrayMenu(startedFromPowerToys, keepDisplayOn, mode);
|
||||
|
||||
InsertAwakeModeMenuItems(mode);
|
||||
|
||||
EnsureDefaultTrayTimeShortcuts(trayTimeShortcuts);
|
||||
CreateAwakeTimeSubMenu(trayTimeShortcuts);
|
||||
}
|
||||
|
||||
private static void ClearExistingTrayMenu()
|
||||
{
|
||||
var destructionStatus = Bridge.DestroyMenu(TrayMenu);
|
||||
if (destructionStatus != true)
|
||||
if (TrayMenu != IntPtr.Zero && !Bridge.DestroyMenu(TrayMenu))
|
||||
{
|
||||
Logger.LogError("Failed to destroy menu.");
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
Logger.LogError($"Failed to destroy menu: {errorCode}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateNewTrayMenu(bool startedFromPowerToys, bool keepDisplayOn, AwakeMode mode)
|
||||
{
|
||||
TrayMenu = Bridge.CreatePopupMenu();
|
||||
|
||||
if (TrayMenu != IntPtr.Zero)
|
||||
if (TrayMenu == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startedFromPowerToys)
|
||||
{
|
||||
// If Awake is started from PowerToys, the correct way to exit it is disabling it from Settings.
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_EXIT, Resources.AWAKE_EXIT);
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
|
||||
InsertMenuItem(0, TrayCommands.TC_EXIT, Resources.AWAKE_EXIT);
|
||||
}
|
||||
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (keepDisplayOn ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, Resources.AWAKE_KEEP_SCREEN_ON);
|
||||
InsertMenuItem(0, TrayCommands.TC_DISPLAY_SETTING, Resources.AWAKE_KEEP_SCREEN_ON, keepDisplayOn, mode == AwakeMode.PASSIVE);
|
||||
InsertSeparator(1);
|
||||
}
|
||||
|
||||
// In case there are no tray shortcuts defined for the application default to a
|
||||
// reasonable initial set.
|
||||
private static void InsertMenuItem(int position, TrayCommands command, string text, bool checkedState = false, bool disabled = false)
|
||||
{
|
||||
uint state = Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING;
|
||||
state |= checkedState ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED;
|
||||
state |= disabled ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED;
|
||||
|
||||
Bridge.InsertMenu(TrayMenu, (uint)position, state, (uint)command, text);
|
||||
}
|
||||
|
||||
private static void InsertSeparator(int position)
|
||||
{
|
||||
Bridge.InsertMenu(TrayMenu, (uint)position, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty);
|
||||
}
|
||||
|
||||
private static void EnsureDefaultTrayTimeShortcuts(Dictionary<string, int> trayTimeShortcuts)
|
||||
{
|
||||
if (trayTimeShortcuts.Count == 0)
|
||||
{
|
||||
trayTimeShortcuts.AddRange(Manager.GetDefaultTrayOptions());
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAwakeTimeSubMenu(Dictionary<string, int> trayTimeShortcuts)
|
||||
{
|
||||
var awakeTimeMenu = Bridge.CreatePopupMenu();
|
||||
for (int i = 0; i < trayTimeShortcuts.Count; i++)
|
||||
{
|
||||
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_SEPARATOR, 0, string.Empty);
|
||||
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, Resources.AWAKE_OFF);
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.INDEFINITE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, Resources.AWAKE_KEEP_INDEFINITELY);
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (mode == AwakeMode.TIMED ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL);
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | Native.Constants.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, Resources.AWAKE_KEEP_UNTIL_EXPIRATION);
|
||||
|
||||
TrayIcon.Text = text;
|
||||
Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP, (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL);
|
||||
}
|
||||
|
||||
private sealed class CheckButtonToolStripMenuItemAccessibleObject : ToolStripItem.ToolStripItemAccessibleObject
|
||||
private static void InsertAwakeModeMenuItems(AwakeMode mode)
|
||||
{
|
||||
private readonly CheckButtonToolStripMenuItem _menuItem;
|
||||
InsertSeparator(0);
|
||||
|
||||
public CheckButtonToolStripMenuItemAccessibleObject(CheckButtonToolStripMenuItem menuItem)
|
||||
: base(menuItem)
|
||||
{
|
||||
_menuItem = menuItem;
|
||||
}
|
||||
|
||||
public override AccessibleRole Role => AccessibleRole.CheckButton;
|
||||
|
||||
public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? Resources.AWAKE_CHECKED : Resources.AWAKE_UNCHECKED);
|
||||
}
|
||||
|
||||
private sealed class CheckButtonToolStripMenuItem : ToolStripMenuItem
|
||||
{
|
||||
protected override AccessibleObject CreateAccessibilityInstance()
|
||||
{
|
||||
return new CheckButtonToolStripMenuItemAccessibleObject(this);
|
||||
}
|
||||
InsertMenuItem(0, TrayCommands.TC_MODE_PASSIVE, Resources.AWAKE_OFF, mode == AwakeMode.PASSIVE);
|
||||
InsertMenuItem(0, TrayCommands.TC_MODE_INDEFINITE, Resources.AWAKE_KEEP_INDEFINITELY, mode == AwakeMode.INDEFINITE);
|
||||
InsertMenuItem(0, TrayCommands.TC_MODE_EXPIRABLE, Resources.AWAKE_KEEP_UNTIL_EXPIRATION, mode == AwakeMode.EXPIRABLE, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,172 +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.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Awake.Core.Models;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Awake.Core
|
||||
{
|
||||
public class TrayMessageFilter : IMessageFilter
|
||||
{
|
||||
private static SettingsUtils? _moduleSettings;
|
||||
|
||||
private static SettingsUtils? ModuleSettings { get => _moduleSettings; set => _moduleSettings = value; }
|
||||
|
||||
private static ManualResetEvent? _exitSignal;
|
||||
|
||||
public TrayMessageFilter(ManualResetEvent? exitSignal)
|
||||
{
|
||||
_exitSignal = exitSignal;
|
||||
ModuleSettings = new SettingsUtils();
|
||||
}
|
||||
|
||||
public bool PreFilterMessage(ref Message m)
|
||||
{
|
||||
var trayCommandsSize = Enum.GetNames(typeof(TrayCommands)).Length;
|
||||
|
||||
switch (m.Msg)
|
||||
{
|
||||
case (int)Native.Constants.WM_COMMAND:
|
||||
var targetCommandIndex = m.WParam.ToInt64() & 0xFFFF;
|
||||
switch (targetCommandIndex)
|
||||
{
|
||||
case (long)TrayCommands.TC_EXIT:
|
||||
ExitCommandHandler(_exitSignal);
|
||||
break;
|
||||
case (long)TrayCommands.TC_DISPLAY_SETTING:
|
||||
DisplaySettingCommandHandler(Constants.AppName);
|
||||
break;
|
||||
case (long)TrayCommands.TC_MODE_INDEFINITE:
|
||||
IndefiniteKeepAwakeCommandHandler(Constants.AppName);
|
||||
break;
|
||||
case (long)TrayCommands.TC_MODE_PASSIVE:
|
||||
PassiveKeepAwakeCommandHandler(Constants.AppName);
|
||||
break;
|
||||
case var _ when targetCommandIndex >= trayCommandsSize:
|
||||
// Format for the timer block:
|
||||
// TrayCommands.TC_TIME + ZERO_BASED_INDEX_IN_SETTINGS
|
||||
AwakeSettings settings = ModuleSettings!.GetSettings<AwakeSettings>(Constants.AppName);
|
||||
if (settings.Properties.CustomTrayTimes.Count == 0)
|
||||
{
|
||||
settings.Properties.CustomTrayTimes.AddRange(Manager.GetDefaultTrayOptions());
|
||||
}
|
||||
|
||||
int index = (int)targetCommandIndex - (int)TrayCommands.TC_TIME;
|
||||
var targetTime = settings.Properties.CustomTrayTimes.ElementAt(index).Value;
|
||||
TimedKeepAwakeCommandHandler(Constants.AppName, targetTime);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ExitCommandHandler(ManualResetEvent? exitSignal)
|
||||
{
|
||||
Manager.CompleteExit(0, exitSignal, true);
|
||||
}
|
||||
|
||||
private static void DisplaySettingCommandHandler(string moduleName)
|
||||
{
|
||||
AwakeSettings currentSettings;
|
||||
|
||||
try
|
||||
{
|
||||
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed GetSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
currentSettings = new AwakeSettings();
|
||||
}
|
||||
|
||||
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
|
||||
|
||||
try
|
||||
{
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed SaveSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TimedKeepAwakeCommandHandler(string moduleName, int seconds)
|
||||
{
|
||||
TimeSpan timeSpan = TimeSpan.FromSeconds(seconds);
|
||||
|
||||
AwakeSettings currentSettings;
|
||||
|
||||
try
|
||||
{
|
||||
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed GetSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
currentSettings = new AwakeSettings();
|
||||
}
|
||||
|
||||
currentSettings.Properties.Mode = AwakeMode.TIMED;
|
||||
currentSettings.Properties.IntervalHours = (uint)timeSpan.Hours;
|
||||
currentSettings.Properties.IntervalMinutes = (uint)timeSpan.Minutes;
|
||||
|
||||
try
|
||||
{
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed SaveSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PassiveKeepAwakeCommandHandler(string moduleName)
|
||||
{
|
||||
Manager.SetPassiveKeepAwakeMode(moduleName);
|
||||
}
|
||||
|
||||
private static void IndefiniteKeepAwakeCommandHandler(string moduleName)
|
||||
{
|
||||
AwakeSettings currentSettings;
|
||||
|
||||
try
|
||||
{
|
||||
currentSettings = ModuleSettings!.GetSettings<AwakeSettings>(moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed GetSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
currentSettings = new AwakeSettings();
|
||||
}
|
||||
|
||||
currentSettings.Properties.Mode = AwakeMode.INDEFINITE;
|
||||
|
||||
try
|
||||
{
|
||||
ModuleSettings!.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"Failed SaveSettings: {ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ using System.Threading.Tasks;
|
||||
using Awake.Core;
|
||||
using Awake.Core.Models;
|
||||
using Awake.Core.Native;
|
||||
using Awake.Properties;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
@ -25,13 +26,7 @@ namespace Awake
|
||||
{
|
||||
internal sealed class Program
|
||||
{
|
||||
// PowerToys Awake build code name. Used for exact logging
|
||||
// that does not map to PowerToys broad version schema to pinpoint
|
||||
// internal issues easier.
|
||||
// Format of the build ID is: CODENAME_MMDDYYYY, where MMDDYYYY
|
||||
// is representative of the date when the last change was made before
|
||||
// the pull request is issued.
|
||||
private static readonly string BuildId = "ATRIOX_04132023";
|
||||
private static readonly ManualResetEvent _exitSignal = new(false);
|
||||
|
||||
private static Mutex? _mutex;
|
||||
private static FileSystemWatcher? _watcher;
|
||||
@ -46,12 +41,11 @@ namespace Awake
|
||||
private static SystemPowerCapabilities _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);
|
||||
internal static readonly string[] AliasesConfigOption = new[] { "--use-pt-config", "-c" };
|
||||
internal static readonly string[] AliasesDisplayOption = new[] { "--display-on", "-d" };
|
||||
internal static readonly string[] AliasesTimeOption = new[] { "--time-limit", "-t" };
|
||||
internal static readonly string[] AliasesPidOption = new[] { "--pid", "-p" };
|
||||
internal static readonly string[] AliasesExpireAtOption = new[] { "--expire-at", "-e" };
|
||||
internal static readonly string[] AliasesConfigOption = ["--use-pt-config", "-c"];
|
||||
internal static readonly string[] AliasesDisplayOption = ["--display-on", "-d"];
|
||||
internal static readonly string[] AliasesTimeOption = ["--time-limit", "-t"];
|
||||
internal static readonly string[] AliasesPidOption = ["--pid", "-p"];
|
||||
internal static readonly string[] AliasesExpireAtOption = ["--expire-at", "-e"];
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
@ -73,7 +67,7 @@ namespace Awake
|
||||
|
||||
Logger.LogInfo($"Launching {Core.Constants.AppName}...");
|
||||
Logger.LogInfo(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
|
||||
Logger.LogInfo($"Build: {BuildId}");
|
||||
Logger.LogInfo($"Build: {Core.Constants.BuildId}");
|
||||
Logger.LogInfo($"OS: {Environment.OSVersion}");
|
||||
Logger.LogInfo($"OS Build: {Manager.GetOperatingSystemBuild()}");
|
||||
|
||||
@ -90,69 +84,47 @@ namespace Awake
|
||||
|
||||
Logger.LogInfo("Parsing parameters...");
|
||||
|
||||
Option<bool> configOption = new(
|
||||
aliases: AliasesConfigOption,
|
||||
getDefaultValue: () => false,
|
||||
description: $"Specifies whether {Core.Constants.AppName} will be using the PowerToys configuration file for managing the state.")
|
||||
var configOption = new Option<bool>(AliasesConfigOption, () => false, Resources.AWAKE_CMD_HELP_CONFIG_OPTION)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsRequired = false,
|
||||
};
|
||||
|
||||
Option<bool> displayOption = new(
|
||||
aliases: AliasesDisplayOption,
|
||||
getDefaultValue: () => true,
|
||||
description: "Determines whether the display should be kept awake.")
|
||||
var displayOption = new Option<bool>(AliasesDisplayOption, () => true, Resources.AWAKE_CMD_HELP_DISPLAY_OPTION)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsRequired = false,
|
||||
};
|
||||
|
||||
Option<uint> timeOption = new(
|
||||
aliases: AliasesTimeOption,
|
||||
getDefaultValue: () => 0,
|
||||
description: "Determines the interval, in seconds, during which the computer is kept awake.")
|
||||
var timeOption = new Option<uint>(AliasesTimeOption, () => 0, Resources.AWAKE_CMD_HELP_TIME_OPTION)
|
||||
{
|
||||
Arity = ArgumentArity.ExactlyOne,
|
||||
IsRequired = false,
|
||||
};
|
||||
|
||||
Option<int> pidOption = new(
|
||||
aliases: AliasesPidOption,
|
||||
getDefaultValue: () => 0,
|
||||
description: $"Bind the execution of {Core.Constants.AppName} to another process. When the process ends, the system will resume managing the current sleep and display state.")
|
||||
var pidOption = new Option<int>(AliasesPidOption, () => 0, Resources.AWAKE_CMD_HELP_PID_OPTION)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsRequired = false,
|
||||
};
|
||||
|
||||
Option<string> expireAtOption = new(
|
||||
aliases: AliasesExpireAtOption,
|
||||
getDefaultValue: () => string.Empty,
|
||||
description: $"Determines the end date/time when {Core.Constants.AppName} will back off and let the system manage the current sleep and display state.")
|
||||
var expireAtOption = new Option<string>(AliasesExpireAtOption, () => string.Empty, Resources.AWAKE_CMD_HELP_EXPIRE_AT_OPTION)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrOne,
|
||||
IsRequired = false,
|
||||
};
|
||||
|
||||
RootCommand? rootCommand = new()
|
||||
{
|
||||
RootCommand? rootCommand =
|
||||
[
|
||||
configOption,
|
||||
displayOption,
|
||||
timeOption,
|
||||
pidOption,
|
||||
expireAtOption,
|
||||
};
|
||||
];
|
||||
|
||||
rootCommand.Description = Core.Constants.AppName;
|
||||
|
||||
rootCommand.SetHandler(
|
||||
HandleCommandLineArguments,
|
||||
configOption,
|
||||
displayOption,
|
||||
timeOption,
|
||||
pidOption,
|
||||
expireAtOption);
|
||||
rootCommand.SetHandler(HandleCommandLineArguments, configOption, displayOption, timeOption, pidOption, expireAtOption);
|
||||
|
||||
return rootCommand.InvokeAsync(args).Result;
|
||||
}
|
||||
@ -160,7 +132,7 @@ namespace Awake
|
||||
private static bool ExitHandler(ControlType ctrlType)
|
||||
{
|
||||
Logger.LogInfo($"Exited through handler with control type: {ctrlType}");
|
||||
Exit("Exiting from the internal termination handler.", Environment.ExitCode, _exitSignal);
|
||||
Exit(Resources.AWAKE_EXIT_MESSAGE, Environment.ExitCode, _exitSignal);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -201,27 +173,30 @@ namespace Awake
|
||||
{
|
||||
// Configuration file is used, therefore we disregard any other command-line parameter
|
||||
// and instead watch for changes in the file.
|
||||
Manager.IsUsingPowerToysConfig = true;
|
||||
|
||||
try
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.ManualReset, interop.Constants.AwakeExitEvent());
|
||||
new Thread(() =>
|
||||
{
|
||||
if (WaitHandle.WaitAny(new WaitHandle[] { _exitSignal, eventHandle }) == 1)
|
||||
if (WaitHandle.WaitAny([_exitSignal, eventHandle]) == 1)
|
||||
{
|
||||
Exit("Received a signal to end the process. Making sure we quit...", 0, _exitSignal, true);
|
||||
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}");
|
||||
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
string? errorString = $"The settings file does not exist. Scaffolding default configuration...";
|
||||
Logger.LogError("The settings file does not exist. Scaffolding default configuration...");
|
||||
|
||||
AwakeSettings scaffoldSettings = new AwakeSettings();
|
||||
AwakeSettings scaffoldSettings = new();
|
||||
_settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), Core.Constants.AppName);
|
||||
}
|
||||
|
||||
@ -229,8 +204,7 @@ namespace Awake
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}";
|
||||
Logger.LogError(errorString);
|
||||
Logger.LogError($"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -241,24 +215,13 @@ namespace Awake
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime expirationDateTime = DateTime.Parse(expireAt, CultureInfo.CurrentCulture);
|
||||
if (expirationDateTime > DateTime.Now)
|
||||
{
|
||||
// We want to have a dedicated expirable keep-awake logic instead of
|
||||
// converting the target date to seconds and then passing to SetupTimedKeepAwake
|
||||
// because that way we're accounting for the user potentially changing their clock
|
||||
// while Awake is running.
|
||||
DateTimeOffset expirationDateTime = DateTimeOffset.Parse(expireAt, CultureInfo.CurrentCulture);
|
||||
Logger.LogInfo($"Operating in thread ID {Environment.CurrentManagedThreadId}.");
|
||||
SetupExpirableKeepAwake(expirationDateTime, displayOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo($"Target date is not in the future, therefore there is nothing to wait for.");
|
||||
}
|
||||
Manager.SetExpirableKeepAwake(expirationDateTime, displayOn);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Could not parse date string {expireAt} into a viable date.");
|
||||
Logger.LogError($"Could not parse date string {expireAt} into a DateTimeOffset object.");
|
||||
Logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
@ -268,11 +231,11 @@ namespace Awake
|
||||
|
||||
if (mode == AwakeMode.INDEFINITE)
|
||||
{
|
||||
SetupIndefiniteKeepAwake(displayOn);
|
||||
Manager.SetIndefiniteKeepAwake(displayOn);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupTimedKeepAwake(timeLimit, displayOn);
|
||||
Manager.SetTimedKeepAwake(timeLimit, displayOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -282,7 +245,7 @@ namespace Awake
|
||||
RunnerHelper.WaitForPowerToysRunner(pid, () =>
|
||||
{
|
||||
Logger.LogInfo($"Triggered PID-based exit handler for PID {pid}.");
|
||||
Exit("Terminating from process binding hook.", 0, _exitSignal, true);
|
||||
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0, _exitSignal, true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -293,33 +256,34 @@ namespace Awake
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(settingsPath)!;
|
||||
var fileName = Path.GetFileName(settingsPath);
|
||||
|
||||
_watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = Path.GetDirectoryName(settingsPath)!,
|
||||
Path = directory,
|
||||
EnableRaisingEvents = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime,
|
||||
Filter = Path.GetFileName(settingsPath),
|
||||
Filter = fileName,
|
||||
};
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? changedObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
var mergedObservable = Observable.Merge(
|
||||
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
h => _watcher.Changed += h,
|
||||
h => _watcher.Changed -= h);
|
||||
h => _watcher.Changed -= h),
|
||||
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
h => _watcher.Created += h,
|
||||
h => _watcher.Created -= h));
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? createdObservable = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
|
||||
cre => _watcher.Created += cre,
|
||||
cre => _watcher.Created -= cre);
|
||||
|
||||
IObservable<System.Reactive.EventPattern<FileSystemEventArgs>>? mergedObservable = Observable.Merge(changedObservable, createdObservable);
|
||||
|
||||
mergedObservable.Throttle(TimeSpan.FromMilliseconds(25))
|
||||
mergedObservable
|
||||
.Throttle(TimeSpan.FromMilliseconds(25))
|
||||
.SubscribeOn(TaskPoolScheduler.Default)
|
||||
.Select(e => e.EventArgs)
|
||||
.Subscribe(HandleAwakeConfigChange);
|
||||
|
||||
TrayHelper.SetTray(Core.Constants.FullAppName, new AwakeSettings(), _startedFromPowerToys);
|
||||
var settings = Manager.ModuleSettings!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? new AwakeSettings();
|
||||
TrayHelper.SetTray(settings, _startedFromPowerToys);
|
||||
|
||||
// Initially the file might not be updated, so we need to start processing
|
||||
// settings right away.
|
||||
ProcessSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -328,99 +292,64 @@ namespace Awake
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupIndefiniteKeepAwake(bool displayOn)
|
||||
{
|
||||
Manager.SetIndefiniteKeepAwake(displayOn);
|
||||
}
|
||||
|
||||
private static void HandleAwakeConfigChange(FileSystemEventArgs fileEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Detected a settings file change. Updating configuration...");
|
||||
Logger.LogInfo("Resetting keep-awake to normal state due to settings change.");
|
||||
ProcessSettings();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Could not handle Awake configuration change. Error: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
AwakeSettings settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName);
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
var settings = _settingsUtils!.GetSettings<AwakeSettings>(Core.Constants.AppName) ?? throw new InvalidOperationException("Settings are null.");
|
||||
Logger.LogInfo($"Identified custom time shortcuts for the tray: {settings.Properties.CustomTrayTimes.Count}");
|
||||
|
||||
switch (settings.Properties.Mode)
|
||||
{
|
||||
case AwakeMode.PASSIVE:
|
||||
{
|
||||
SetupNoKeepAwake();
|
||||
Manager.SetPassiveKeepAwake();
|
||||
break;
|
||||
}
|
||||
|
||||
case AwakeMode.INDEFINITE:
|
||||
{
|
||||
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
|
||||
Manager.SetIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
|
||||
break;
|
||||
}
|
||||
|
||||
case AwakeMode.TIMED:
|
||||
{
|
||||
uint computedTime = (settings.Properties.IntervalHours * 60 * 60) + (settings.Properties.IntervalMinutes * 60);
|
||||
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
|
||||
|
||||
Manager.SetTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
|
||||
break;
|
||||
}
|
||||
|
||||
case AwakeMode.EXPIRABLE:
|
||||
// When we are loading from the settings file, let's make sure that we never
|
||||
// get users in a state where the expirable keep-awake is in the past.
|
||||
if (settings.Properties.ExpirationDateTime <= DateTimeOffset.Now)
|
||||
{
|
||||
SetupExpirableKeepAwake(settings.Properties.ExpirationDateTime, settings.Properties.KeepDisplayOn);
|
||||
|
||||
break;
|
||||
settings.Properties.ExpirationDateTime = DateTimeOffset.Now.AddMinutes(5);
|
||||
_settingsUtils.SaveSettings(JsonSerializer.Serialize(settings), Core.Constants.AppName);
|
||||
}
|
||||
|
||||
Manager.SetExpirableKeepAwake(settings.Properties.ExpirationDateTime, settings.Properties.KeepDisplayOn);
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
string? errorMessage = "Unknown mode of operation. Check config file.";
|
||||
Logger.LogError(errorMessage);
|
||||
Logger.LogError("Unknown mode of operation. Check config file.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TrayHelper.SetTray(Core.Constants.FullAppName, settings, _startedFromPowerToys);
|
||||
}
|
||||
else
|
||||
{
|
||||
string? errorMessage = "Settings are null.";
|
||||
Logger.LogError(errorMessage);
|
||||
}
|
||||
TrayHelper.SetTray(settings, _startedFromPowerToys);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string? errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
|
||||
Logger.LogError(errorMessage);
|
||||
Logger.LogError($"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupNoKeepAwake()
|
||||
{
|
||||
Logger.LogInfo($"Operating in passive mode (computer's standard power plan). No custom keep awake settings enabled.");
|
||||
|
||||
Manager.SetNoKeepAwake();
|
||||
}
|
||||
|
||||
private static void SetupExpirableKeepAwake(DateTimeOffset expireAt, bool displayOn)
|
||||
{
|
||||
Logger.LogInfo($"Expirable keep-awake. Expected expiration date/time: {expireAt} with display on setting set to {displayOn}.");
|
||||
|
||||
Manager.SetExpirableKeepAwake(expireAt, displayOn);
|
||||
}
|
||||
|
||||
private static void SetupTimedKeepAwake(uint time, bool displayOn)
|
||||
{
|
||||
Logger.LogInfo($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
|
||||
|
||||
Manager.SetTimedKeepAwake(time, displayOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
126
src/modules/awake/Awake/Properties/Resources.Designer.cs
generated
126
src/modules/awake/Awake/Properties/Resources.Designer.cs
generated
@ -60,24 +60,6 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 1 hour.
|
||||
/// </summary>
|
||||
internal static string AWAKE_1_HOUR {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_1_HOUR", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to 1 minute.
|
||||
/// </summary>
|
||||
internal static string AWAKE_1_MINUTE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_1_MINUTE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Checked.
|
||||
/// </summary>
|
||||
@ -87,6 +69,51 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Specifies whether Awake will be using the PowerToys configuration file for managing the state..
|
||||
/// </summary>
|
||||
internal static string AWAKE_CMD_HELP_CONFIG_OPTION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_CMD_HELP_CONFIG_OPTION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Determines whether the display should be kept awake..
|
||||
/// </summary>
|
||||
internal static string AWAKE_CMD_HELP_DISPLAY_OPTION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_CMD_HELP_DISPLAY_OPTION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Determines the end date and time when Awake will back off and let the system manage the current sleep and display state..
|
||||
/// </summary>
|
||||
internal static string AWAKE_CMD_HELP_EXPIRE_AT_OPTION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_CMD_HELP_EXPIRE_AT_OPTION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bind the execution of Awake to another process. When the process ends, the system will resume managing the current sleep and display state..
|
||||
/// </summary>
|
||||
internal static string AWAKE_CMD_HELP_PID_OPTION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_CMD_HELP_PID_OPTION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Determines the interval (in seconds) during which the computer is kept awake..
|
||||
/// </summary>
|
||||
internal static string AWAKE_CMD_HELP_TIME_OPTION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_CMD_HELP_TIME_OPTION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exit.
|
||||
/// </summary>
|
||||
@ -96,6 +123,33 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Terminating from process binding hook..
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_BINDING_HOOK_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_BINDING_HOOK_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exiting from the internal termination handler..
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Received a signal to end the process. Making sure we quit....
|
||||
/// </summary>
|
||||
internal static string AWAKE_EXIT_SIGNAL_MESSAGE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_EXIT_SIGNAL_MESSAGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} hours.
|
||||
/// </summary>
|
||||
@ -159,6 +213,42 @@ namespace Awake.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Expiring.
|
||||
/// </summary>
|
||||
internal static string AWAKE_TRAY_TEXT_EXPIRATION {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_TRAY_TEXT_EXPIRATION", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Indefinite.
|
||||
/// </summary>
|
||||
internal static string AWAKE_TRAY_TEXT_INDEFINITE {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_TRAY_TEXT_INDEFINITE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passive.
|
||||
/// </summary>
|
||||
internal static string AWAKE_TRAY_TEXT_OFF {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_TRAY_TEXT_OFF", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Timed.
|
||||
/// </summary>
|
||||
internal static string AWAKE_TRAY_TEXT_TIMED {
|
||||
get {
|
||||
return ResourceManager.GetString("AWAKE_TRAY_TEXT_TIMED", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unchecked.
|
||||
/// </summary>
|
||||
|
@ -123,9 +123,6 @@
|
||||
<data name="AWAKE_EXIT" xml:space="preserve">
|
||||
<value>Exit</value>
|
||||
</data>
|
||||
<data name="AWAKE_1_HOUR" xml:space="preserve">
|
||||
<value>1 hour</value>
|
||||
</data>
|
||||
<data name="AWAKE_HOURS" xml:space="preserve">
|
||||
<value>{0} hours</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
@ -145,9 +142,6 @@
|
||||
<value>Keep awake until expiration date and time</value>
|
||||
<comment>Keep the system awake until expiration date and time</comment>
|
||||
</data>
|
||||
<data name="AWAKE_1_MINUTE" xml:space="preserve">
|
||||
<value>1 minute</value>
|
||||
</data>
|
||||
<data name="AWAKE_MINUTES" xml:space="preserve">
|
||||
<value>{0} minutes</value>
|
||||
<comment>{0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake.</comment>
|
||||
@ -159,4 +153,40 @@
|
||||
<data name="AWAKE_UNCHECKED" xml:space="preserve">
|
||||
<value>Unchecked</value>
|
||||
</data>
|
||||
<data name="AWAKE_CMD_HELP_CONFIG_OPTION" xml:space="preserve">
|
||||
<value>Specifies whether Awake will be using the PowerToys configuration file for managing the state.</value>
|
||||
</data>
|
||||
<data name="AWAKE_CMD_HELP_DISPLAY_OPTION" xml:space="preserve">
|
||||
<value>Determines whether the display should be kept awake.</value>
|
||||
</data>
|
||||
<data name="AWAKE_CMD_HELP_EXPIRE_AT_OPTION" xml:space="preserve">
|
||||
<value>Determines the end date and time when Awake will back off and let the system manage the current sleep and display state.</value>
|
||||
</data>
|
||||
<data name="AWAKE_CMD_HELP_PID_OPTION" xml:space="preserve">
|
||||
<value>Bind the execution of Awake to another process. When the process ends, the system will resume managing the current sleep and display state.</value>
|
||||
</data>
|
||||
<data name="AWAKE_CMD_HELP_TIME_OPTION" xml:space="preserve">
|
||||
<value>Determines the interval (in seconds) during which the computer is kept awake.</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_BINDING_HOOK_MESSAGE" xml:space="preserve">
|
||||
<value>Terminating from process binding hook.</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_MESSAGE" xml:space="preserve">
|
||||
<value>Exiting from the internal termination handler.</value>
|
||||
</data>
|
||||
<data name="AWAKE_EXIT_SIGNAL_MESSAGE" xml:space="preserve">
|
||||
<value>Received a signal to end the process. Making sure we quit...</value>
|
||||
</data>
|
||||
<data name="AWAKE_TRAY_TEXT_EXPIRATION" xml:space="preserve">
|
||||
<value>Expiring</value>
|
||||
</data>
|
||||
<data name="AWAKE_TRAY_TEXT_INDEFINITE" xml:space="preserve">
|
||||
<value>Indefinite</value>
|
||||
</data>
|
||||
<data name="AWAKE_TRAY_TEXT_OFF" xml:space="preserve">
|
||||
<value>Passive</value>
|
||||
</data>
|
||||
<data name="AWAKE_TRAY_TEXT_TIMED" xml:space="preserve">
|
||||
<value>Timed</value>
|
||||
</data>
|
||||
</root>
|
14
src/settings-ui/Settings.UI.Library/AwakeMode.cs
Normal file
14
src/settings-ui/Settings.UI.Library/AwakeMode.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// 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 Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public enum AwakeMode
|
||||
{
|
||||
PASSIVE = 0,
|
||||
INDEFINITE = 1,
|
||||
TIMED = 2,
|
||||
EXPIRABLE = 3,
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
IntervalHours = 0;
|
||||
IntervalMinutes = 1;
|
||||
ExpirationDateTime = DateTimeOffset.Now;
|
||||
CustomTrayTimes = new Dictionary<string, int>();
|
||||
CustomTrayTimes = [];
|
||||
}
|
||||
|
||||
[JsonPropertyName("keepDisplayOn")]
|
||||
@ -40,12 +40,4 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[CmdConfigureIgnoreAttribute]
|
||||
public Dictionary<string, int> CustomTrayTimes { get; set; }
|
||||
}
|
||||
|
||||
public enum AwakeMode
|
||||
{
|
||||
PASSIVE = 0,
|
||||
INDEFINITE = 1,
|
||||
TIMED = 2,
|
||||
EXPIRABLE = 3,
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
@ -12,12 +13,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public class AwakeSettings : BasePTModuleSettings, ISettingsConfig, ICloneable
|
||||
{
|
||||
public const string ModuleName = "Awake";
|
||||
public const string ModuleVersion = "0.0.2";
|
||||
|
||||
public AwakeSettings()
|
||||
{
|
||||
Name = ModuleName;
|
||||
Version = ModuleVersion;
|
||||
Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
Properties = new AwakeProperties();
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
x:Uid="Awake_EnableSettingsCard"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
|
@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
@ -66,20 +65,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsExpirationConfigurationEnabled
|
||||
{
|
||||
get => ModuleSettings.Properties.Mode == AwakeMode.EXPIRABLE && IsEnabled;
|
||||
}
|
||||
public bool IsExpirationConfigurationEnabled => ModuleSettings.Properties.Mode == AwakeMode.EXPIRABLE && IsEnabled;
|
||||
|
||||
public bool IsTimeConfigurationEnabled
|
||||
{
|
||||
get => ModuleSettings.Properties.Mode == AwakeMode.TIMED && IsEnabled;
|
||||
}
|
||||
public bool IsTimeConfigurationEnabled => ModuleSettings.Properties.Mode == AwakeMode.TIMED && IsEnabled;
|
||||
|
||||
public bool IsScreenConfigurationPossibleEnabled
|
||||
{
|
||||
get => ModuleSettings.Properties.Mode != AwakeMode.PASSIVE && IsEnabled;
|
||||
}
|
||||
public bool IsScreenConfigurationPossibleEnabled => ModuleSettings.Properties.Mode != AwakeMode.PASSIVE && IsEnabled;
|
||||
|
||||
public AwakeMode Mode
|
||||
{
|
||||
@ -90,6 +80,26 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
ModuleSettings.Properties.Mode = value;
|
||||
|
||||
if (value == AwakeMode.TIMED && IntervalMinutes == 0 && IntervalHours == 0)
|
||||
{
|
||||
// Handle the special case where both hours and minutes are zero.
|
||||
// Otherwise, this will reset to passive very quickly in the UI.
|
||||
ModuleSettings.Properties.IntervalMinutes = 1;
|
||||
OnPropertyChanged(nameof(IntervalMinutes));
|
||||
}
|
||||
else if (value == AwakeMode.EXPIRABLE && ExpirationDateTime <= DateTimeOffset.Now)
|
||||
{
|
||||
// To make sure that we're not tracking expirable keep-awake in the past,
|
||||
// let's make sure that every time it's enabled from the settings UI, it's
|
||||
// five (5) minutes into the future.
|
||||
ExpirationDateTime = DateTimeOffset.Now.AddMinutes(5);
|
||||
|
||||
// The expiration date/time is updated and will send the notification
|
||||
// but we need to do this manually for the expiration time that is
|
||||
// bound to the time control on the settings page.
|
||||
OnPropertyChanged(nameof(ExpirationTime));
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(IsTimeConfigurationEnabled));
|
||||
OnPropertyChanged(nameof(IsScreenConfigurationPossibleEnabled));
|
||||
OnPropertyChanged(nameof(IsExpirationConfigurationEnabled));
|
||||
|
Loading…
Reference in New Issue
Block a user