Merge remote-tracking branch 'origin/master'

This commit is contained in:
Den Delimarsky 2021-05-11 14:44:52 -07:00
commit a78da90282
No known key found for this signature in database
GPG key ID: E1BE1355085F0BCF
10 changed files with 329 additions and 164 deletions

View file

@ -2,13 +2,13 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Win32;
using NLog;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
namespace Espresso.Shell.Core
{
@ -18,7 +18,7 @@ namespace Espresso.Shell.Core
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001
ES_SYSTEM_REQUIRED = 0x00000001,
}
/// <summary>
@ -27,16 +27,15 @@ namespace Espresso.Shell.Core
/// </summary>
public class APIHelper
{
private const string BUILD_REGISTRY_LOCATION = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
private static CancellationTokenSource TokenSource;
private static CancellationToken ThreadToken;
private static readonly Logger log;
private static readonly Logger _log;
private static CancellationTokenSource _tokenSource;
private static CancellationToken _threadToken;
// More details about the API used: https://docs.microsoft.com/windows/win32/api/winbase/nf-winbase-setthreadexecutionstate
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
// More details about the API used: https://docs.microsoft.com/windows/win32/api/shellapi/nf-shellapi-extracticonexw
[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
@ -44,8 +43,8 @@ namespace Espresso.Shell.Core
static APIHelper()
{
log = LogManager.GetCurrentClassLogger();
TokenSource = new CancellationTokenSource();
_log = LogManager.GetCurrentClassLogger();
_tokenSource = new CancellationTokenSource();
}
/// <summary>
@ -60,23 +59,14 @@ namespace Espresso.Shell.Core
try
{
var stateResult = SetThreadExecutionState(state);
bool stateSettingSucceeded = (stateResult != 0);
if (stateSettingSucceeded)
{
return true;
}
else
{
return false;
}
return stateResult != 0;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to reset the current machine state to one where Espresso doesn't try to keep it awake.
/// This does not interfere with the state that can be potentially set by other applications.
@ -84,7 +74,7 @@ namespace Espresso.Shell.Core
/// <returns>Status of the attempt. True is successful, false if not.</returns>
public static bool SetNormalKeepAwake()
{
TokenSource.Cancel();
_tokenSource.Cancel();
return SetAwakeState(EXECUTION_STATE.ES_CONTINUOUS);
}
@ -95,33 +85,9 @@ namespace Espresso.Shell.Core
/// <returns>Status of the attempt. True if successful, false if not.</returns>
public static bool SetIndefiniteKeepAwake(bool keepDisplayOn = true)
{
TokenSource = new CancellationTokenSource();
ThreadToken = TokenSource.Token;
//Put thread for indefinite loop.
}
public static void SetTimedKeepAwake(long seconds, Action<bool> callback, Action failureCallback, bool keepDisplayOn = true)
{
TokenSource = new CancellationTokenSource();
ThreadToken = TokenSource.Token;
Task.Run(() => RunTimedLoop(seconds, keepDisplayOn), ThreadToken)
.ContinueWith((result) => callback(result.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion); ;
}
private static void RunIndefiniteLoop(bool keepDisplayOn = true)
{
bool success = false;
// In case cancellation was already requested.
ThreadToken.ThrowIfCancellationRequested();
if (keepDisplayOn)
{
SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
return SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
}
else
{
@ -129,13 +95,22 @@ namespace Espresso.Shell.Core
}
}
public static void SetTimedKeepAwake(long seconds, Action<bool> callback, Action failureCallback, bool keepDisplayOn = true)
{
_tokenSource = new CancellationTokenSource();
_threadToken = _tokenSource.Token;
Task.Run(() => RunTimedLoop(seconds, keepDisplayOn), _threadToken)
.ContinueWith((result) => callback(result.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
}
private static bool RunTimedLoop(long seconds, bool keepDisplayOn = true)
{
bool success = false;
// In case cancellation was already requested.
ThreadToken.ThrowIfCancellationRequested();
_threadToken.ThrowIfCancellationRequested();
try
{
if (keepDisplayOn)
@ -143,20 +118,21 @@ namespace Espresso.Shell.Core
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
if (success)
{
log.Info("Timed keep-awake with display on.");
_log.Info("Timed keep-awake with display on.");
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(seconds))
{
if (ThreadToken.IsCancellationRequested)
if (_threadToken.IsCancellationRequested)
{
ThreadToken.ThrowIfCancellationRequested();
_threadToken.ThrowIfCancellationRequested();
}
}
return success;
}
else
{
log.Info("Could not set up timed keep-awake with display on.");
_log.Info("Could not set up timed keep-awake with display on.");
return success;
}
}
@ -165,20 +141,21 @@ namespace Espresso.Shell.Core
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
if (success)
{
log.Info("Timed keep-awake with display off.");
_log.Info("Timed keep-awake with display off.");
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(seconds))
{
if (ThreadToken.IsCancellationRequested)
if (_threadToken.IsCancellationRequested)
{
ThreadToken.ThrowIfCancellationRequested();
_threadToken.ThrowIfCancellationRequested();
}
}
return success;
}
else
{
log.Info("Could not set up timed keep-awake with display off.");
_log.Info("Could not set up timed keep-awake with display off.");
return success;
}
}
@ -186,7 +163,7 @@ namespace Espresso.Shell.Core
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
log.Info($"Background thread termination. Message: {ex.Message}");
_log.Info($"Background thread termination. Message: {ex.Message}");
return success;
}
}
@ -196,7 +173,7 @@ namespace Espresso.Shell.Core
try
{
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BUILD_REGISTRY_LOCATION);
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BuildRegistryLocation);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
if (registryKey != null)
@ -206,18 +183,17 @@ namespace Espresso.Shell.Core
}
else
{
log.Debug("Registry key acquisition for OS failed.");
_log.Debug("Registry key acquisition for OS failed.");
return string.Empty;
}
}
catch (Exception ex)
{
log.Debug($"Could not get registry key for the build number. Error: {ex.Message}");
_log.Debug($"Could not get registry key for the build number. Error: {ex.Message}");
return string.Empty;
}
}
public static Icon? Extract(string file, int number, bool largeIcon)
{
ExtractIconEx(file, number, out IntPtr large, out IntPtr small, 1);
@ -229,8 +205,6 @@ namespace Espresso.Shell.Core
{
return null;
}
}
}
}

View file

@ -2,11 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Library;
using System;
using System.Drawing;
using System.Text.Json;
using System.Windows.Forms;
using Microsoft.PowerToys.Settings.UI.Library;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
@ -15,10 +15,12 @@ namespace Espresso.Shell.Core
{
internal static class TrayHelper
{
static NotifyIcon? trayIcon;
private static NotifyIcon? trayIcon;
private static NotifyIcon TrayIcon { get => trayIcon; set => trayIcon = value; }
static SettingsUtils? moduleSettings;
private static SettingsUtils? moduleSettings;
private static SettingsUtils ModuleSettings { get => moduleSettings; set => moduleSettings = value; }
static TrayHelper()
@ -29,7 +31,8 @@ namespace Espresso.Shell.Core
public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null)
{
System.Threading.Tasks.Task.Factory.StartNew((tray) =>
System.Threading.Tasks.Task.Factory.StartNew(
(tray) =>
{
((NotifyIcon?)tray).Text = text;
((NotifyIcon?)tray).Icon = icon;
@ -42,31 +45,51 @@ namespace Espresso.Shell.Core
internal static void SetTray(string text, EspressoSettings settings)
{
SetTray(text, settings.Properties.KeepDisplayOn.Value, settings.Properties.Mode,
() => {
// Set indefinite keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.INDEFINITE;
SetTray(
text,
settings.Properties.KeepDisplayOn.Value,
settings.Properties.Mode,
IndefiniteKeepAwakeCallback(text),
TimedKeepAwakeCallback(text),
KeepDisplayOnCallback(text));
}
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
},
(hours, minutes) => {
// Set timed keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.TIMED;
currentSettings.Properties.Hours.Value = hours;
currentSettings.Properties.Minutes.Value = minutes;
private static Action KeepDisplayOnCallback(string text)
{
return () =>
{
// Just changing the display mode.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.KeepDisplayOn.Value = !currentSettings.Properties.KeepDisplayOn.Value;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
},
() =>
{
// Just changing the display mode.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.KeepDisplayOn.Value = !currentSettings.Properties.KeepDisplayOn.Value;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
};
}
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
});
private static Action<int, int> TimedKeepAwakeCallback(string text)
{
return (hours, minutes) =>
{
// Set timed keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.TIMED;
currentSettings.Properties.Hours.Value = hours;
currentSettings.Properties.Minutes.Value = minutes;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
};
}
private static Action IndefiniteKeepAwakeCallback(string text)
{
return () =>
{
// Set indefinite keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(text);
currentSettings.Properties.Mode = EspressoMode.INDEFINITE;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), text);
};
}
internal static void SetTray(string text, bool keepDisplayOn, EspressoMode mode, Action indefiniteKeepAwakeCallback, Action<int, int> timedKeepAwakeCallback, Action keepDisplayOnCallback)
@ -76,13 +99,13 @@ namespace Espresso.Shell.Core
// Main toolstrip.
var operationContextMenu = new ToolStripMenuItem
{
Text = "Mode"
Text = "Mode",
};
// Indefinite keep-awake menu item.
var indefiniteMenuItem = new ToolStripMenuItem
{
Text = "Indefinite"
Text = "Indefinite",
};
if (mode == EspressoMode.INDEFINITE)
@ -93,6 +116,7 @@ namespace Espresso.Shell.Core
{
indefiniteMenuItem.Checked = false;
}
indefiniteMenuItem.Click += (e, s) =>
{
// User opted to set the mode to indefinite, so we need to write new settings.
@ -101,7 +125,7 @@ namespace Espresso.Shell.Core
var displayOnMenuItem = new ToolStripMenuItem
{
Text = "Keep display on"
Text = "Keep display on",
};
if (keepDisplayOn)
{
@ -111,6 +135,7 @@ namespace Espresso.Shell.Core
{
displayOnMenuItem.Checked = false;
}
displayOnMenuItem.Click += (e, s) =>
{
// User opted to set the display mode directly.
@ -120,12 +145,12 @@ namespace Espresso.Shell.Core
// Timed keep-awake menu item
var timedMenuItem = new ToolStripMenuItem
{
Text = "Timed"
Text = "Timed",
};
var halfHourMenuItem = new ToolStripMenuItem
{
Text = "30 minutes"
Text = "30 minutes",
};
halfHourMenuItem.Click += (e, s) =>
{
@ -135,7 +160,7 @@ namespace Espresso.Shell.Core
var oneHourMenuItem = new ToolStripMenuItem
{
Text = "1 hour"
Text = "1 hour",
};
oneHourMenuItem.Click += (e, s) =>
{
@ -145,7 +170,7 @@ namespace Espresso.Shell.Core
var twoHoursMenuItem = new ToolStripMenuItem
{
Text = "2 hours"
Text = "2 hours",
};
twoHoursMenuItem.Click += (e, s) =>
{

View file

@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
@ -12,12 +12,7 @@
<!--Per documentation: https://docs.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.Espresso</AssemblyName>
<ApplicationIcon>Espresso.ico</ApplicationIcon>
<Company>Den Delimarsky</Company>
<Authors>Den Delimarsky</Authors>
<Product>Espresso</Product>
<PackageProjectUrl>https://espresso.den.dev</PackageProjectUrl>
<PackageIcon>Espresso.png</PackageIcon>
<Version>$(Version).0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -66,5 +61,19 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers">
<Version>1.1.118</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View file

@ -2,10 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Espresso.Shell.Core;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
@ -16,40 +12,44 @@ using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reflection;
using System.Threading;
using Espresso.Shell.Core;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using NLog;
#pragma warning disable CS8602 // Dereference of a possibly null reference.
#pragma warning disable CS8603 // Possible null reference return.
namespace Espresso.Shell
{
class Program
internal class Program
{
private static Mutex? mutex = null;
private const string appName = "Espresso";
private static FileSystemWatcher? watcher = null;
private static SettingsUtils? settingsUtils = null;
private static Mutex? _mutex = null;
private const string AppName = "Espresso";
private static FileSystemWatcher? _watcher = null;
private static SettingsUtils? _settingsUtils = null;
public static Mutex Mutex { get => mutex; set => mutex = value; }
public static Mutex LockMutex { get => _mutex; set => _mutex = value; }
private static Logger? log;
private static Logger? _log;
static int Main(string[] args)
private static int Main(string[] args)
{
bool instantiated;
Mutex = new Mutex(true, appName, out instantiated);
LockMutex = new Mutex(true, AppName, out instantiated);
if (!instantiated)
{
ForceExit(appName + " is already running! Exiting the application.", 1);
ForceExit(AppName + " is already running! Exiting the application.", 1);
}
log = LogManager.GetCurrentClassLogger();
settingsUtils = new SettingsUtils();
_log = LogManager.GetCurrentClassLogger();
_settingsUtils = new SettingsUtils();
log.Info("Launching Espresso...");
log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
log.Debug($"OS: {Environment.OSVersion}");
log.Debug($"OS Build: {APIHelper.GetOperatingSystemBuild()}");
_log.Info("Launching Espresso...");
_log.Info(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
_log.Debug($"OS: {Environment.OSVersion}");
_log.Debug($"OS Build: {APIHelper.GetOperatingSystemBuild()}");
var configOption = new Option<bool>(
aliases: new[] { "--use-pt-config", "-c" },
@ -108,10 +108,10 @@ namespace Espresso.Shell
configOption,
displayOption,
timeOption,
pidOption
pidOption,
};
rootCommand.Description = appName;
rootCommand.Description = AppName;
rootCommand.Handler = CommandHandler.Create<bool, bool, long, int>(HandleCommandLineArguments);
@ -120,21 +120,21 @@ namespace Espresso.Shell
private static void ForceExit(string message, int exitCode)
{
log.Debug(message);
log.Info(message);
_log.Debug(message);
_log.Info(message);
Console.ReadKey();
Environment.Exit(exitCode);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, long timeLimit, int pid)
{
log.Info($"The value for --use-pt-config is: {usePtConfig}");
log.Info($"The value for --display-on is: {displayOn}");
log.Info($"The value for --time-limit is: {timeLimit}");
log.Info($"The value for --pid is: {pid}");
_log.Info($"The value for --use-pt-config is: {usePtConfig}");
_log.Info($"The value for --display-on is: {displayOn}");
_log.Info($"The value for --time-limit is: {timeLimit}");
_log.Info($"The value for --pid is: {pid}");
#pragma warning disable CS8604 // Possible null reference argument.
TrayHelper.InitializeTray(appName, APIHelper.Extract("shell32.dll", 32, true));
TrayHelper.InitializeTray(AppName, APIHelper.Extract("shell32.dll", 32, true));
#pragma warning restore CS8604 // Possible null reference argument.
if (usePtConfig)
@ -143,21 +143,20 @@ namespace Espresso.Shell
// and instead watch for changes in the file.
try
{
var settingsPath = settingsUtils.GetSettingsFilePath(appName);
log.Info($"Reading configuration file: {settingsPath}");
var settingsPath = _settingsUtils.GetSettingsFilePath(AppName);
_log.Info($"Reading configuration file: {settingsPath}");
watcher = new FileSystemWatcher
_watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(settingsPath),
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite,
Filter = Path.GetFileName(settingsPath)
Filter = Path.GetFileName(settingsPath),
};
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => watcher.Changed += h,
h => watcher.Changed -= h
)
h => _watcher.Changed += h,
h => _watcher.Changed -= h)
.SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs)
.Throttle(TimeSpan.FromMilliseconds(25))
@ -170,8 +169,8 @@ namespace Espresso.Shell
catch (Exception ex)
{
var errorString = $"There was a problem with the configuration file. Make sure it exists.\n{ex.Message}";
log.Info(errorString);
log.Debug(errorString);
_log.Info(errorString);
_log.Debug(errorString);
}
}
else
@ -204,20 +203,20 @@ namespace Espresso.Shell
bool success = APIHelper.SetIndefiniteKeepAwake(displayOn);
if (success)
{
log.Info($"Currently in indefinite keep awake. Display always on: {displayOn}");
_log.Info($"Currently in indefinite keep awake. Display always on: {displayOn}");
}
else
{
var errorMessage = "Could not set up the state to be indefinite keep awake.";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
}
private static void HandleEspressoConfigChange(FileSystemEventArgs fileEvent)
{
log.Info("Detected a settings file change. Updating configuration...");
log.Info("Resetting keep-awake to normal state due to settings change.");
_log.Info("Detected a settings file change. Updating configuration...");
_log.Info("Resetting keep-awake to normal state due to settings change.");
ResetNormalPowerState();
ProcessSettings();
}
@ -226,7 +225,7 @@ namespace Espresso.Shell
{
try
{
EspressoSettings settings = settingsUtils.GetSettings<EspressoSettings>(appName);
EspressoSettings settings = _settingsUtils.GetSettings<EspressoSettings>(AppName);
if (settings != null)
{
@ -238,6 +237,7 @@ namespace Espresso.Shell
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn.Value);
break;
}
case EspressoMode.TIMED:
{
// Timed keep-awake.
@ -246,35 +246,36 @@ namespace Espresso.Shell
break;
}
default:
{
var errorMessage = "Unknown mode of operation. Check config file.";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
break;
}
}
TrayHelper.SetTray(appName, settings);
TrayHelper.SetTray(AppName, settings);
}
else
{
var errorMessage = "Settings are null.";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
}
catch (Exception ex)
{
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.Message}";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
}
private static void SetupTimedKeepAwake(long time, bool displayOn)
{
log.Info($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
_log.Info($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
APIHelper.SetTimedKeepAwake(time, LogTimedKeepAwakeCompletion, LogUnexpectedOrCancelledKeepAwakeCompletion, displayOn);
}
@ -282,13 +283,13 @@ namespace Espresso.Shell
private static void LogUnexpectedOrCancelledKeepAwakeCompletion()
{
var errorMessage = "The keep-awake thread was terminated early.";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
private static void LogTimedKeepAwakeCompletion(bool result)
{
log.Info($"Completed timed keep-awake successfully: {result}");
_log.Info($"Completed timed keep-awake successfully: {result}");
}
private static void ResetNormalPowerState()
@ -296,13 +297,13 @@ namespace Espresso.Shell
bool success = APIHelper.SetNormalKeepAwake();
if (success)
{
log.Info("Returned to normal keep-awake state.");
_log.Info("Returned to normal keep-awake state.");
}
else
{
var errorMessage = "Could not return to normal keep-awake state.";
log.Info(errorMessage);
log.Debug(errorMessage);
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
}
}

View file

@ -123,6 +123,9 @@
<Compile Include="OOBE\Enums\PowerToysModulesEnum.cs" />
<Compile Include="OOBE\ViewModel\OobeShellViewModel.cs" />
<Compile Include="OOBE\ViewModel\OobePowerToysModule.cs" />
<Compile Include="OOBE\Views\OobeEspresso.xaml.cs">
<DependentUpon>OobeEspresso.xaml</DependentUpon>
</Compile>
<Compile Include="OOBE\Views\OobeColorPicker.xaml.cs">
<DependentUpon>OobeColorPicker.xaml</DependentUpon>
</Compile>
@ -300,6 +303,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="OOBE\Views\OobeEspresso.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="OOBE\Views\OobeColorPicker.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View file

@ -8,6 +8,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
{
Overview = 0,
ColorPicker,
Espresso,
FancyZones,
FileExplorer,
ImageResizer,

View file

@ -0,0 +1,70 @@
<Page x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeEspresso"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="280" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Stretch="UniformToFill"
Source="{x:Bind ViewModel.PreviewImageSource}" />
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical"
Margin="{StaticResource OobePageContentMargin}"
VerticalAlignment="Top">
<TextBlock Text="{x:Bind ViewModel.ModuleName}"
AutomationProperties.HeadingLevel="Level2"
Style="{StaticResource PageTitleStyle}" />
<TextBlock TextWrapping="Wrap"
Margin="0,4,0,0"
Text="{x:Bind ViewModel.Description}" />
<HyperlinkButton NavigateUri="{x:Bind ViewModel.Link}" Margin="0,0,0,4">
<TextBlock>
<Run x:Uid="Oobe_LearnMore" />
<Run Text="{x:Bind ViewModel.ModuleName}" />
</TextBlock>
</HyperlinkButton>
<TextBlock x:Uid="Oobe_HowToUse"
AutomationProperties.HeadingLevel="Level3"
Style="{StaticResource OobeSubtitleStyle}" />
<controls:ShortcutTextControl x:Uid="Oobe_Espresso_HowToUse" />
<TextBlock x:Uid="Oobe_TipsAndTricks"
AutomationProperties.HeadingLevel="Level3"
Style="{StaticResource OobeSubtitleStyle}"/>
<controls:ShortcutTextControl x:Uid="Oobe_Espresso_TipsAndTricks" />
<StackPanel Orientation="Horizontal"
Spacing="4"
Margin="0,32,0,0">
<Button Click="SettingsLaunchButton_Click"
AutomationProperties.LabeledBy="{Binding ElementName=SettingsLabel}">
<StackPanel Orientation="Horizontal"
Spacing="8">
<TextBlock Text="&#xE115;"
Margin="0,3,0,0"
FontFamily="Segoe MDL2 Assets" />
<TextBlock x:Uid="OOBE_Settings" Name="SettingsLabel" />
</StackPanel>
</Button>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View file

@ -0,0 +1,48 @@
// 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.Threading;
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
using Microsoft.PowerToys.Settings.UI.Views;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class OobeEspresso : Page
{
public OobePowerToysModule ViewModel { get; set; }
public OobeEspresso()
{
this.InitializeComponent();
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModulesEnum.Espresso]);
DataContext = ViewModel;
}
private void SettingsLaunchButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if (OobeShellPage.OpenMainWindowCallback != null)
{
OobeShellPage.OpenMainWindowCallback(typeof(EspressoPage));
}
ViewModel.LogOpeningSettingsEvent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.LogClosingModuleEvent();
}
}
}

View file

@ -88,6 +88,18 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
Description = loader.GetString("Oobe_ColorPicker_Description"),
Link = "https://aka.ms/PowerToysOverview_ColorPicker",
});
Modules.Insert((int)PowerToysModulesEnum.Espresso, new OobePowerToysModule()
{
ModuleName = loader.GetString("Oobe_Espresso"),
Tag = "Espresso",
IsNew = false,
Icon = "\uEC32",
Image = "ms-appx:///Assets/Modules/Espresso.png",
FluentIcon = "ms-appx:///Assets/FluentIcons/FluentIconsEspresso.png",
PreviewImageSource = "ms-appx:///Assets/Modules/OOBE/FancyZones.gif",
Description = loader.GetString("Oobe_Espresso_Description"),
Link = "https://aka.ms/PowerToysOverview_Espresso",
});
Modules.Insert((int)PowerToysModulesEnum.FancyZones, new OobePowerToysModule()
{
ModuleName = loader.GetString("Oobe_FancyZones"),
@ -211,6 +223,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
{
case "Overview": NavigationFrame.Navigate(typeof(OobeOverview)); break;
case "ColorPicker": NavigationFrame.Navigate(typeof(OobeColorPicker)); break;
case "Espresso": NavigationFrame.Navigate(typeof(OobeEspresso)); break;
case "FancyZones": NavigationFrame.Navigate(typeof(OobeFancyZones)); break;
case "Run": NavigationFrame.Navigate(typeof(OobeRun)); break;
case "ImageResizer": NavigationFrame.Navigate(typeof(OobeImageResizer)); break;

View file

@ -909,6 +909,10 @@
<value>https://aka.ms/PowerToysOverview_ColorPicker</value>
<comment>URL. Do not loc</comment>
</data>
<data name="Espresso_ImageHyperlinkToDocs.NavigateUri" xml:space="preserve">
<value>https://aka.ms/PowerToysOverview_Espresso</value>
<comment>URL. Do not loc</comment>
</data>
<data name="FancyZones_ImageHyperlinkToDocs.NavigateUri" xml:space="preserve">
<value>https://aka.ms/PowerToysOverview_FancyZones</value>
<comment>URL. Do not loc</comment>
@ -1208,4 +1212,17 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and
<value>https://espresso.den.dev</value>
<comment>URL. Do not loc</comment>
</data>
<data name="Oobe_Espresso" xml:space="preserve">
<value>Espresso</value>
<comment>Module name, do not loc</comment>
</data>
<data name="Oobe_Espresso_Description" xml:space="preserve">
<value>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</value>
</data>
<data name="Oobe_Espresso_HowToUse.Text" xml:space="preserve">
<value>Open PowerToys Settings and {turn on Espresso}</value>
</data>
<data name="Oobe_Espresso_TipsAndTricks.Text" xml:space="preserve">
<value>You can always change modes quickly by clicking the {Espresso icon in the system tray}</value>
</data>
</root>