Merge branch 'master' into master

This commit is contained in:
Clint Rutkas 2021-05-12 10:08:51 -07:00 committed by GitHub
commit 803522ac7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2242 additions and 29 deletions

View file

@ -87,6 +87,7 @@ args
argv
Arial
arik
arity
arjunbalgovind
ARPINSTALLLOCATION
ARPPRODUCTICON
@ -123,6 +124,7 @@ Autorun
AUTOSIZECOLUMNS
autoupdate
AValid
AWAYMODE
azurecr
azurewebsites
backend
@ -1077,6 +1079,8 @@ localport
localtime
LOCATIONCHANGE
Lockyour
logconsole
logfile
LOGFONT
LOGMSG
logon
@ -1191,6 +1195,7 @@ mimetype
Minimizeallwindows
MINIMIZEBOX
miniz
minlevel
MINMAXINFO
Miracast
MJPG
@ -1410,6 +1415,7 @@ otating
OUTOFCONTEXT
OUTOFMEMORY
Outptr
outputtype
outro
outsettings
OVERLAPPEDWINDOW
@ -1982,6 +1988,7 @@ toggleswitch
toolbar
Toolchain
toolset
toolstrip
tooltip
toolwindow
TOPDOWNDIB
@ -2188,6 +2195,8 @@ windowsx
windowwalker
winerror
WINEVENT
winexe
winforms
winfx
winget
Winhook
@ -2275,6 +2284,7 @@ XOffset
xpath
XResource
xsd
xsi
XSmall
XStr
XToolset

View file

@ -324,6 +324,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrar
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorTest", "src\modules\keyboardmanager\KeyboardManagerEditorTest\KeyboardManagerEditorTest.vcxproj", "{62173D9A-6724-4C00-A1C8-FB646480A9EC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "espresso", "espresso", "{127F38E0-40AA-4594-B955-5616BF206882}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EspressoModuleInterface", "src\modules\espresso\EspressoModuleInterface\EspressoModuleInterface.vcxproj", "{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Espresso", "src\modules\espresso\Espresso\Espresso.csproj", "{D940E07F-532C-4FF3-883F-790DA014F19A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -660,6 +666,14 @@ Global
{62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.Build.0 = Debug|x64
{62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.ActiveCfg = Release|x64
{62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.Build.0 = Release|x64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.ActiveCfg = Debug|x64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Debug|x64.Build.0 = Debug|x64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.ActiveCfg = Release|x64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.Build.0 = Release|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.ActiveCfg = Debug|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.Build.0 = Debug|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.ActiveCfg = Release|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -759,6 +773,9 @@ Global
{8DF78B53-200E-451F-9328-01EB907193AE} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{23D2070D-E4AD-4ADD-85A7-083D9C76AD49} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{127F38E0-40AA-4594-B955-5616BF206882} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {127F38E0-40AA-4594-B955-5616BF206882}
{D940E07F-532C-4FF3-883F-790DA014F19A} = {127F38E0-40AA-4594-B955-5616BF206882}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -2,12 +2,14 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<!-- Names of folders and projects -->
<?define FancyZonesProjectName="FancyZones"?>
<?define ImageResizerProjectName="ImageResizer"?>
<?define KeyboardManagerProjectName="KeyboardManager"?>
<?define PowerRenameProjectName="PowerRename"?>
<?define ShortcutGuideProjectName="ShortcutGuide"?>
<?define ColorPickerProjectName="ColorPicker"?>
<?define EspressoProjectName="Espresso"?>
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
<?define BinX64Dir="$(var.RepoDir)x64\$(var.Configuration)\" ?>
@ -212,16 +214,26 @@
<Directory Id="ShortcutGuideInstallFolder" Name="$(var.ShortcutGuideProjectName)"/>
<Directory Id="FileExplorerPreviewInstallFolder" Name="FileExplorerPreview" />
<Directory Id="FancyZonesInstallFolder" Name="$(var.FancyZonesProjectName)" />
<Directory Id="EspressoInstallFolder" Name="$(var.EspressoProjectName)" />
<!-- KBM -->
<Directory Id="KeyboardManagerInstallFolder" Name="$(var.KeyboardManagerProjectName)">
<Directory Id="KeyboardManagerEditorInstallFolder" Name="KeyboardManagerEditor" />
<Directory Id="KeyboardManagerEngineInstallFolder" Name="KeyboardManagerEngine" />
</Directory>
<!-- Color Picker -->
<Directory Id="ColorPickerInstallFolder" Name="$(var.ColorPickerProjectName)">
<Directory Id="ColorPickerResourcesFolder" Name="Resources"/>
</Directory>
<!-- Launcher -->
<Directory Id="LauncherInstallFolder" Name="launcher">
<Directory Id="AssetsFolder" Name="Assets" />
<Directory Id="LauncherImagesFolder" Name="Images" />
<Directory Id="LauncherPropertiesFolder" Name="Properties" />
<!-- Plugins -->
<Directory Id="LauncherPluginsFolder" Name="Plugins">
<Directory Id="CalculatorPluginFolder" Name="Calculator">
<Directory Id="CalculatorImagesFolder" Name="Images" />
@ -266,9 +278,10 @@
<Directory Id="SystemImagesFolder" Name="Images" />
</Directory>
</Directory>
<Directory Id="LauncherPropertiesFolder" Name="Properties" />
</Directory>
</Directory>
<!-- Settings -->
<Directory Id="SettingsV2InstallFolder" Name="Settings">
<Directory Id="SettingsV2ViewsInstallFolder" Name="Views"/>
<Directory Id="SettingsV2StylesInstallFolder" Name="Styles"/>
@ -355,6 +368,7 @@
</Component>
</DirectoryRef>
<!-- Shortcut guide files -->
<DirectoryRef Id="SvgsInstallFolder" FileSource="$(var.BinX64Dir)svgs\">
<Component Id="PowerToysSvgs" Guid="7C4D4EED-9338-423D-992C-DCE02F3E2D35" Win64="yes">
<File Source="$(var.BinX64Dir)svgs\0.svg" />
@ -372,6 +386,8 @@
<File Source="$(var.BinX64Dir)svgs\overlay_portrait.svg" />
</Component>
</DirectoryRef>
<!-- FancyZone -->
<DirectoryRef Id="FancyZonesInstallFolder" FileSource="$(var.BinX64Dir)modules\">
<Component Id="Module_FancyZones" Guid="C6B5272E-6ED4-4B80-B0E7-2FF0355D8CF4" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.FancyZonesProjectName)\fancyzones.dll" KeyPath="yes" />
@ -477,6 +493,7 @@
</Component>
</DirectoryRef>
<!-- Image Resizer -->
<DirectoryRef Id="ImageResizerInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.ImageResizerProjectName)">
<Component Id="Module_ImageResizer" Guid="96E63289-759C-4A73-A56B-EE7429932F72" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.ImageResizerProjectName)\ImageResizer.exe" />
@ -506,6 +523,7 @@
<RegistryValue Value="[ImageResizerInstallFolder]ImageResizerExt.dll" Type="string" />
<RegistryValue Name="ThreadingModel" Value="Apartment" Type="string" />
</RegistryKey>
<!-- Registry Key for the drag and drop handler -->
<RegistryValue Root="HKCR"
Key="Directory\ShellEx\DragDropHandlers\ImageResizer"
@ -567,6 +585,7 @@
</Component>
</DirectoryRef>
<!-- PowerRename -->
<DirectoryRef Id="PowerRenameInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.PowerRenameProjectName)">
<Component Id="Module_PowerRename" Guid="E4401D08-27FE-4F96-BA17-0C61FD79E684" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.PowerRenameProjectName)\PowerRenameExt.dll" KeyPath="yes" />
@ -582,30 +601,35 @@
</Component>
</DirectoryRef>
<!-- Shortcut guide -->
<DirectoryRef Id="ShortcutGuideInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.ShortcutGuideProjectName)\">
<Component Id="Module_ShortcutGuide" Guid="CBD0AC09-91D3-428E-B2B3-05745ADF3473" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.ShortcutGuideProjectName)\$(var.ShortcutGuideProjectName).dll" KeyPath="yes" />
</Component>
</DirectoryRef>
<!-- KBM -->
<DirectoryRef Id="KeyboardManagerInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\">
<Component Id="Module_KeyboardManager" Guid="9279BD82-786F-4F0B-8E49-DB484EE34C9B" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\KeyboardManager.dll" />
</Component>
</DirectoryRef>
<!-- KBM Editor -->
<DirectoryRef Id="KeyboardManagerEditorInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\KeyboardManagerEditor">
<Component Id="Module_KeyboardManager_Editor" Guid="1240F1B8-17FE-4D68-B9AF-91882B0B1933" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\KeyboardManagerEditor\PowerToys.KeyboardManagerEditor.exe" />
</Component>
</DirectoryRef>
<!-- KBM Engine -->
<DirectoryRef Id="KeyboardManagerEngineInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\KeyboardManagerEngine">
<Component Id="Module_KeyboardManager_Engine" Guid="14DBAA38-B98D-431F-9439-8EDE1C0670DB" Win64="yes">
<File Source="$(var.BinX64Dir)modules\$(var.KeyboardManagerProjectName)\KeyboardManagerEngine\PowerToys.KeyboardManagerEngine.exe" />
</Component>
</DirectoryRef>
<!-- Color Picker -->
<DirectoryRef Id="ColorPickerInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)">
<Component Id="Module_ColorPicker" Guid="8A52A69E-37B2-4BEA-9D73-77763066052F" Win64="yes">
<?foreach File in ColorPicker.dll;System.IO.Abstractions.dll;ColorPickerUI.exe;ColorPickerUI.dll;ColorPickerUI.deps.json;ColorPickerUI.runtimeconfig.json;Microsoft.PowerToys.Settings.UI.Lib.dll;PowerToysInterop.dll;System.Text.Json.dll;ManagedTelemetry.dll;ManagedCommon.dll;ControlzEx.dll;Microsoft.Xaml.Behaviors.dll;ModernWpf.Controls.dll;ModernWpf.dll;System.ComponentModel.Composition.dll;Microsoft.PowerToys.Common.UI.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Text.Encodings.Web.dll?>
@ -614,6 +638,7 @@
</Component>
</DirectoryRef>
<!-- Color Picker Resources -->
<DirectoryRef Id="ColorPickerResourcesFolder" FileSource="$(var.BinX64Dir)modules\$(var.ColorPickerProjectName)\Resources">
<Component Id="Module_ColorPicker_Resources" Guid="7544BD0F-1DB6-4C53-89D3-ADAD472FDCC1">
<?foreach File in colorPicker.cur;icon.ico?>
@ -621,6 +646,27 @@
<?endforeach?>
</Component>
</DirectoryRef>
<!-- Espresso -->
<DirectoryRef Id="EspressoInstallFolder" FileSource="$(var.BinX64Dir)modules\$(var.EspressoProjectName)">
<Component Id="Module_Espresso" Guid="F26F5780-5B38-43B2-BC21-8406ED6E2071" Win64="yes">
<!--
EspressoModuleInterface.exp;
EspressoModuleInterface.lib;
EspressoModuleInterface.pdb;
ManagedCommon.pdb;
ManagedTelemetry.pdb;
Microsoft.PowerToys.Settings.UI.Lib.pdb;
PowerToys.Espresso.pdb;
PowerToys.Espresso.runtimeconfig.dev.json;
PowerToysInterop.pdb;
System.Reactive.xml;
-->
<?foreach File in EspressoModuleInterface.dll;ManagedCommon.dll;ManagedTelemetry.dll;Microsoft.PowerToys.Settings.UI.Lib.dll;Microsoft.Win32.Registry.dll;Microsoft.Win32.SystemEvents.dll;NLog.config;NLog.dll;PowerToys.Espresso.deps.json;PowerToys.Espresso.dll;PowerToys.Espresso.exe;PowerToys.Espresso.runtimeconfig.json;PowerToysInterop.dll;System.CommandLine.dll;System.Configuration.ConfigurationManager.dll;System.Drawing.Common.dll;System.IO.Abstractions.dll;System.Reactive.dll;System.Runtime.Caching.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Security.AccessControl.dll;System.Security.Cryptography.ProtectedData.dll;System.Security.Permissions.dll;System.Security.Principal.Windows.dll;System.Text.Encodings.Web.dll;System.Text.Json.dll;System.Windows.Extensions.dll?>
<File Id="EspressoFile_$(var.File)" Source="$(var.BinX64Dir)modules\$(var.EspressoProjectName)\$(var.File)" />
<?endforeach?>
</Component>
</DirectoryRef>
<DirectoryRef Id="FileExplorerPreviewInstallFolder" FileSource="$(var.RepoDir)\modules\FileExplorerPreview\">
<Component Id="Module_PowerPreview" Guid="FF1700D5-1B07-4E07-9A62-4D206645EEA9" Win64="yes">
@ -800,6 +846,7 @@
<ComponentRef Id="Module_KeyboardManager_Engine" />
<ComponentRef Id="Module_ColorPicker" />
<ComponentRef Id="Module_ColorPicker_Resources"/>
<ComponentRef Id ="Module_Espresso"/>
<ComponentRef Id="SettingsV2" />
<ComponentRef Id="SettingsV2Assets" />
<ComponentRef Id="SettingsV2AssetsModules" />

View file

@ -8,6 +8,7 @@ namespace PTSettingsHelper
{
constexpr inline const wchar_t* log_settings_filename = L"log_settings.json";
std::wstring get_module_save_file_location(std::wstring_view powertoy_key);
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
std::wstring get_root_save_folder_location();

View file

@ -0,0 +1,245 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using NLog;
namespace Espresso.Shell.Core
{
[Flags]
public enum EXECUTION_STATE : uint
{
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001,
}
/// <summary>
/// Helper class that allows talking to Win32 APIs without having to rely on PInvoke in other parts
/// of the codebase.
/// </summary>
public class APIHelper
{
private const string BuildRegistryLocation = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
public const int StdOutputHandle = -11;
public const int StdInputHandle = -10;
public const int StdErrorHandle = -12;
public const uint GenericWrite = 0x40000000;
public const uint GenericRead = 0x80000000;
private static readonly Logger _log;
private static CancellationTokenSource _tokenSource;
private static CancellationToken _threadToken;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] uint access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
static APIHelper()
{
_log = LogManager.GetCurrentClassLogger();
_tokenSource = new CancellationTokenSource();
}
public static void AllocateConsole()
{
AllocConsole();
var outputFilePointer = CreateFile("CONOUT$", GenericRead | GenericWrite, FileShare.Write, IntPtr.Zero, FileMode.OpenOrCreate, 0, IntPtr.Zero);
SetStdHandle(StdOutputHandle, outputFilePointer);
Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true });
}
/// <summary>
/// Sets the computer awake state using the native Win32 SetThreadExecutionState API. This
/// function is just a nice-to-have wrapper that helps avoid tracking the success or failure of
/// the call.
/// </summary>
/// <param name="state">Single or multiple EXECUTION_STATE entries.</param>
/// <returns>true if successful, false if failed</returns>
private static bool SetAwakeState(EXECUTION_STATE state)
{
try
{
var stateResult = SetThreadExecutionState(state);
return stateResult != 0;
}
catch
{
return false;
}
}
public static void SetIndefiniteKeepAwake(Action<bool> callback, Action failureCallback, bool keepDisplayOn = true)
{
_tokenSource = new CancellationTokenSource();
_threadToken = _tokenSource.Token;
Task.Run(() => RunIndefiniteLoop(keepDisplayOn), _threadToken)
.ContinueWith((result) => callback(result.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith((result) => failureCallback, TaskContinuationOptions.NotOnRanToCompletion);
}
public static void SetTimedKeepAwake(uint 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 RunIndefiniteLoop(bool keepDisplayOn = true)
{
bool success;
if (keepDisplayOn)
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
}
else
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
}
try
{
if (success)
{
_log.Info("Initiated indefinite keep awake in background thread.");
while (true)
{
if (_threadToken.IsCancellationRequested)
{
_threadToken.ThrowIfCancellationRequested();
}
}
}
else
{
_log.Info("Could not successfully set up indefinite keep awake.");
return success;
}
}
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
_log.Info($"Background thread termination. Message: {ex.Message}");
return success;
}
}
private static bool RunTimedLoop(uint seconds, bool keepDisplayOn = true)
{
bool success = false;
// In case cancellation was already requested.
_threadToken.ThrowIfCancellationRequested();
try
{
if (keepDisplayOn)
{
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.");
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(Math.Abs(seconds)))
{
if (_threadToken.IsCancellationRequested)
{
_threadToken.ThrowIfCancellationRequested();
}
}
return success;
}
else
{
_log.Info("Could not set up timed keep-awake with display on.");
return success;
}
}
else
{
success = SetAwakeState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
if (success)
{
_log.Info("Timed keep-awake with display off.");
var startTime = DateTime.UtcNow;
while (DateTime.UtcNow - startTime < TimeSpan.FromSeconds(Math.Abs(seconds)))
{
if (_threadToken.IsCancellationRequested)
{
_threadToken.ThrowIfCancellationRequested();
}
}
return success;
}
else
{
_log.Info("Could not set up timed keep-awake with display off.");
return success;
}
}
}
catch (OperationCanceledException ex)
{
// Task was clearly cancelled.
_log.Info($"Background thread termination. Message: {ex.Message}");
return success;
}
}
public static string GetOperatingSystemBuild()
{
try
{
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BuildRegistryLocation);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
if (registryKey != null)
{
var versionString = $"{registryKey.GetValue("ProductName")} {registryKey.GetValue("DisplayVersion")} {registryKey.GetValue("BuildLabEx")}";
return versionString;
}
else
{
_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}");
return string.Empty;
}
}
}
}

View file

@ -0,0 +1,226 @@
// 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.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.
namespace Espresso.Shell.Core
{
internal static class TrayHelper
{
private static NotifyIcon? trayIcon;
private static NotifyIcon TrayIcon { get => trayIcon; set => trayIcon = value; }
private static SettingsUtils? moduleSettings;
private static SettingsUtils ModuleSettings { get => moduleSettings; set => moduleSettings = value; }
static TrayHelper()
{
TrayIcon = new NotifyIcon();
ModuleSettings = new SettingsUtils();
}
public static void InitializeTray(string text, Icon icon, ContextMenuStrip? contextMenu = null)
{
System.Threading.Tasks.Task.Factory.StartNew(
(tray) =>
{
((NotifyIcon?)tray).Text = text;
((NotifyIcon?)tray).Icon = icon;
((NotifyIcon?)tray).ContextMenuStrip = contextMenu;
((NotifyIcon?)tray).Visible = true;
Application.Run();
}, TrayIcon);
}
internal static void SetTray(string text, EspressoSettings settings)
{
SetTray(
text,
settings.Properties.KeepDisplayOn,
settings.Properties.Mode,
IndefiniteKeepAwakeCallback(text),
TimedKeepAwakeCallback(text),
KeepDisplayOnCallback(text),
ExitCallback());
}
private static Action ExitCallback()
{
return () =>
{
Environment.Exit(0);
};
}
private static Action KeepDisplayOnCallback(string moduleName)
{
return () =>
{
// Just changing the display mode.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.KeepDisplayOn = !currentSettings.Properties.KeepDisplayOn;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
private static Action<uint, uint> TimedKeepAwakeCallback(string moduleName)
{
return (hours, minutes) =>
{
// Set timed keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.Mode = EspressoMode.TIMED;
currentSettings.Properties.Hours = hours;
currentSettings.Properties.Minutes = minutes;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
private static Action IndefiniteKeepAwakeCallback(string moduleName)
{
return () =>
{
// Set indefinite keep awake.
var currentSettings = ModuleSettings.GetSettings<EspressoSettings>(moduleName);
currentSettings.Properties.Mode = EspressoMode.INDEFINITE;
ModuleSettings.SaveSettings(JsonSerializer.Serialize(currentSettings), moduleName);
};
}
public static void SetTray(string text, bool keepDisplayOn, EspressoMode mode, Action indefiniteKeepAwakeCallback, Action<uint, uint> timedKeepAwakeCallback, Action keepDisplayOnCallback, Action exitCallback)
{
var contextMenuStrip = new ContextMenuStrip();
// Main toolstrip.
var operationContextMenu = new ToolStripMenuItem
{
Text = "Mode",
};
// Indefinite keep-awake menu item.
var indefiniteMenuItem = new ToolStripMenuItem
{
Text = "Keep awake indefinitely",
};
if (mode == EspressoMode.INDEFINITE)
{
indefiniteMenuItem.Checked = true;
}
else
{
indefiniteMenuItem.Checked = false;
}
indefiniteMenuItem.Click += (e, s) =>
{
// User opted to set the mode to indefinite, so we need to write new settings.
indefiniteKeepAwakeCallback();
};
var displayOnMenuItem = new ToolStripMenuItem
{
Text = "Keep display on",
};
if (keepDisplayOn)
{
displayOnMenuItem.Checked = true;
}
else
{
displayOnMenuItem.Checked = false;
}
displayOnMenuItem.Click += (e, s) =>
{
// User opted to set the display mode directly.
keepDisplayOnCallback();
};
// Timed keep-awake menu item
var timedMenuItem = new ToolStripMenuItem
{
Text = "Keep awake temporarily",
};
if (mode == EspressoMode.TIMED)
{
timedMenuItem.Checked = true;
}
else
{
timedMenuItem.Checked = false;
}
var halfHourMenuItem = new ToolStripMenuItem
{
Text = "30 minutes",
};
halfHourMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 30 minutes.
timedKeepAwakeCallback(0, 30);
};
var oneHourMenuItem = new ToolStripMenuItem
{
Text = "1 hour",
};
oneHourMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 1 hour.
timedKeepAwakeCallback(1, 0);
};
var twoHoursMenuItem = new ToolStripMenuItem
{
Text = "2 hours",
};
twoHoursMenuItem.Click += (e, s) =>
{
// User is setting the keep-awake to 2 hours.
timedKeepAwakeCallback(2, 0);
};
// Exit menu item.
var exitContextMenu = new ToolStripMenuItem
{
Text = "Exit",
};
exitContextMenu.Click += (e, s) =>
{
// User is setting the keep-awake to 2 hours.
exitCallback();
};
timedMenuItem.DropDownItems.Add(halfHourMenuItem);
timedMenuItem.DropDownItems.Add(oneHourMenuItem);
timedMenuItem.DropDownItems.Add(twoHoursMenuItem);
operationContextMenu.DropDownItems.Add(indefiniteMenuItem);
operationContextMenu.DropDownItems.Add(timedMenuItem);
operationContextMenu.DropDownItems.Add(new ToolStripSeparator());
operationContextMenu.DropDownItems.Add(displayOnMenuItem);
contextMenuStrip.Items.Add(operationContextMenu);
contextMenuStrip.Items.Add(new ToolStripSeparator());
contextMenuStrip.Items.Add(exitContextMenu);
TrayIcon.Text = text;
TrayIcon.ContextMenuStrip = contextMenuStrip;
}
}
}

View file

@ -0,0 +1,87 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\Espresso</OutputPath>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWindowsForms>true</UseWindowsForms>
<!--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>
<Version>$(Version).0</Version>
<ApplicationIcon>Images\Espresso.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DefineConstants>TRACE;RELEASE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<None Remove="Images\Espresso.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="NLog" Version="4.7.9" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20071.2" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Runtime.Caching" Version="6.0.0-preview.1.21102.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Microsoft.PowerToys.Settings.UI.Library\Microsoft.PowerToys.Settings.UI.Library.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Program.cs">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<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>
<ItemGroup>
<Resource Include="Images\Espresso.ico" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true">
<target name="logfile" xsi:type="File" fileName="espresso.log" layout="[${longdate} ${level:uppercase=true} ${logger}] ${message}" />
<target name="logconsole" xsi:type="Console" layout="[${longdate} ${level:uppercase=true}] ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logconsole" />
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>

View file

@ -0,0 +1,294 @@
// 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.CommandLine;
using System.CommandLine.Invocation;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
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
{
internal class Program
{
private static Mutex? _mutex = null;
private const string AppName = "Espresso";
private static FileSystemWatcher? _watcher = null;
private static SettingsUtils? _settingsUtils = null;
public static Mutex LockMutex { get => _mutex; set => _mutex = value; }
private static Logger? _log;
private static int Main(string[] args)
{
bool instantiated;
LockMutex = new Mutex(true, AppName, out instantiated);
if (!instantiated)
{
ForceExit(AppName + " is already running! Exiting the application.", 1);
}
_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()}");
var configOption = new Option<bool>(
aliases: new[] { "--use-pt-config", "-c" },
getDefaultValue: () => false,
description: "Specifies whether Espresso will be using the PowerToys configuration file for managing the state.")
{
Argument = new Argument<bool>(() => false)
{
Arity = ArgumentArity.ZeroOrOne,
},
};
configOption.Required = false;
var displayOption = new Option<bool>(
aliases: new[] { "--display-on", "-d" },
getDefaultValue: () => true,
description: "Determines whether the display should be kept awake.")
{
Argument = new Argument<bool>(() => false)
{
Arity = ArgumentArity.ZeroOrOne,
},
};
displayOption.Required = false;
var timeOption = new Option<uint>(
aliases: new[] { "--time-limit", "-t" },
getDefaultValue: () => 0,
description: "Determines the interval, in seconds, during which the computer is kept awake.")
{
Argument = new Argument<uint>(() => 0)
{
Arity = ArgumentArity.ExactlyOne,
},
};
timeOption.Required = false;
var pidOption = new Option<int>(
aliases: new[] { "--pid", "-p" },
getDefaultValue: () => 0,
description: "Bind the execution of Espresso to another process.")
{
Argument = new Argument<int>(() => 0)
{
Arity = ArgumentArity.ZeroOrOne,
},
};
pidOption.Required = false;
var rootCommand = new RootCommand
{
configOption,
displayOption,
timeOption,
pidOption,
};
rootCommand.Description = AppName;
rootCommand.Handler = CommandHandler.Create<bool, bool, uint, int>(HandleCommandLineArguments);
return rootCommand.InvokeAsync(args).Result;
}
private static void ForceExit(string message, int exitCode)
{
_log.Debug(message);
_log.Info(message);
Console.ReadKey();
Environment.Exit(exitCode);
}
private static void HandleCommandLineArguments(bool usePtConfig, bool displayOn, uint timeLimit, int pid)
{
if (pid == 0)
{
APIHelper.AllocateConsole();
}
_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}");
if (usePtConfig)
{
#pragma warning disable CS8604 // Possible null reference argument.
TrayHelper.InitializeTray(AppName, new Icon(Application.GetResourceStream(new Uri("/Images/Espresso.ico", UriKind.Relative)).Stream));
#pragma warning restore CS8604 // Possible null reference argument.
// Configuration file is used, therefore we disregard any other command-line parameter
// and instead watch for changes in the file.
try
{
var settingsPath = _settingsUtils.GetSettingsFilePath(AppName);
_log.Info($"Reading configuration file: {settingsPath}");
_watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(settingsPath),
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite,
Filter = Path.GetFileName(settingsPath),
};
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
h => _watcher.Changed += h,
h => _watcher.Changed -= h)
.SubscribeOn(TaskPoolScheduler.Default)
.Select(e => e.EventArgs)
.Throttle(TimeSpan.FromMilliseconds(25))
.Subscribe(HandleEspressoConfigChange);
// Initially the file might not be updated, so we need to start processing
// settings right away.
ProcessSettings();
}
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);
}
}
else
{
var mode = timeLimit <= 0 ? EspressoMode.INDEFINITE : EspressoMode.TIMED;
if (mode == EspressoMode.INDEFINITE)
{
SetupIndefiniteKeepAwake(displayOn);
}
else
{
SetupTimedKeepAwake(timeLimit, displayOn);
}
}
var exitSignal = new ManualResetEvent(false);
if (pid != 0)
{
RunnerHelper.WaitForPowerToysRunner(pid, () =>
{
exitSignal.Set();
Environment.Exit(0);
});
}
exitSignal.WaitOne();
}
private static void SetupIndefiniteKeepAwake(bool displayOn)
{
// Indefinite keep awake.
APIHelper.SetIndefiniteKeepAwake(LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
}
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.");
ProcessSettings();
}
private static void ProcessSettings()
{
try
{
EspressoSettings settings = _settingsUtils.GetSettings<EspressoSettings>(AppName);
if (settings != null)
{
switch (settings.Properties.Mode)
{
case EspressoMode.INDEFINITE:
{
// Indefinite keep awake.
SetupIndefiniteKeepAwake(settings.Properties.KeepDisplayOn);
break;
}
case EspressoMode.TIMED:
{
// Timed keep-awake.
uint computedTime = (settings.Properties.Hours * 60 * 60) + (settings.Properties.Minutes * 60);
SetupTimedKeepAwake(computedTime, settings.Properties.KeepDisplayOn);
break;
}
default:
{
var errorMessage = "Unknown mode of operation. Check config file.";
_log.Info(errorMessage);
_log.Debug(errorMessage);
break;
}
}
TrayHelper.SetTray(AppName, settings);
}
else
{
var errorMessage = "Settings are null.";
_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);
}
}
private static void SetupTimedKeepAwake(uint time, bool displayOn)
{
_log.Info($"Timed keep-awake. Expected runtime: {time} seconds with display on setting set to {displayOn}.");
APIHelper.SetTimedKeepAwake(time, LogCompletedKeepAwakeThread, LogUnexpectedOrCancelledKeepAwakeThreadCompletion, displayOn);
}
private static void LogUnexpectedOrCancelledKeepAwakeThreadCompletion()
{
var errorMessage = "The keep-awake thread was terminated early.";
_log.Info(errorMessage);
_log.Debug(errorMessage);
}
private static void LogCompletedKeepAwakeThread(bool result)
{
_log.Info($"Exited keep-awake thread successfully: {result}");
}
}
}

View file

@ -0,0 +1,7 @@
#include <string>
namespace EspressoConstants
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"Espresso";
}

View file

@ -0,0 +1,72 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_ESPRESSO_NAME "Espresso"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{5e7360a8-d048-4ed3-8f09-0bfd64c5529a}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>Espresso</RootNamespace>
<ProjectName>EspressoModuleInterface</ProjectName>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\Espresso\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="EspressoConstants.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SetttingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="EspressoModuleInterface.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="EspressoConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Generated Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{e8ef1c4e-cc50-4ce5-b00d-4e3ac5c1a7db}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{fbd9cdd2-e7d5-4417-9b52-25e345ae9562}</UniqueIdentifier>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{c2a23a2b-5846-440f-b29e-eea748dba12d}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{77f1702b-da7f-4ff6-90a3-19db515cf963}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="EspressoModuleInterface.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,226 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/interop/shared_constants.h>
#include "trace.h"
#include "resource.h"
#include "EspressoConstants.h"
#include <common/logger/logger.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/utils/elevation.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
#include <common/utils/os-detect.h>
#include <common/utils/winapi_error.h>
#include <filesystem>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Trace::RegisterProvider();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
// The PowerToy name that will be shown in the settings.
const static wchar_t* MODULE_NAME = L"Espresso";
// Add a description that will we shown in the module settings page.
const static wchar_t* MODULE_DESC = L"<no description>";
// These are the properties shown in the Settings page.
struct ModuleSettings
{
} g_settings;
// Implement the PowerToy Module Interface and all the required methods.
class Espresso : public PowertoyModuleIface
{
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
private:
// The PowerToy state.
bool m_enabled = false;
HANDLE m_hProcess;
HANDLE send_telemetry_event;
// Handle to event used to invoke Espresso
HANDLE m_hInvokeEvent;
// Load initial settings from the persisted values.
void init_settings();
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void launch_process()
{
Logger::trace(L"Launching Espresso process");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"--use-pt-config --pid " + std::to_wstring(powertoys_pid);
Logger::trace(L"Espresso launching with parameters: " + executable_args);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"modules\\Espresso\\PowerToys.Espresso.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (!ShellExecuteExW(&sei))
{
DWORD error = GetLastError();
std::wstring message = L"Espresso failed to start with error = ";
message += std::to_wstring(error);
Logger::error(message);
}
m_hProcess = sei.hProcess;
}
public:
// Constructor
Espresso()
{
app_name = GET_RESOURCE_STRING(IDS_ESPRESSO_NAME);
app_key = EspressoConstants::ModuleKey;
std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key));
Logger::init(LogSettings::launcherLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
Logger::info("Launcher object is constructing");
init_settings();
};
// Destroy the powertoy and free memory
virtual void destroy() override
{
delete this;
}
// Return the display name of the powertoy, this will be cached by the runner
virtual const wchar_t* get_name() override
{
return MODULE_NAME;
}
// Return JSON with the configuration options.
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(MODULE_DESC);
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Return the non localized key of the powertoy, this will be cached by the runner
virtual const wchar_t* get_key() override
{
return app_key.c_str();
}
// Signal from the Settings editor to call a custom action.
// This can be used to spawn more complex editors.
virtual void call_custom_action(const wchar_t* action) override
{
static UINT custom_action_num_calls = 0;
try
{
// Parse the action values, including name.
PowerToysSettings::CustomActionObject action_object =
PowerToysSettings::CustomActionObject::from_json_string(action);
}
catch (std::exception&)
{
// Improper JSON.
}
}
// Called by the runner to pass the updated settings values as a serialized JSON.
virtual void set_config(const wchar_t* config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
values.save_to_settings_file();
}
catch (std::exception&)
{
// Improper JSON.
}
}
virtual void enable()
{
ResetEvent(send_telemetry_event);
ResetEvent(m_hInvokeEvent);
launch_process();
m_enabled = true;
};
virtual void disable()
{
if (m_enabled)
{
ResetEvent(send_telemetry_event);
ResetEvent(m_hInvokeEvent);
TerminateProcess(m_hProcess, 1);
}
m_enabled = false;
}
// Returns if the powertoys is enabled
virtual bool is_enabled() override
{
return m_enabled;
}
};
// Load the settings file.
void Espresso::init_settings()
{
try
{
// Load and parse the settings file for this PowerToy.
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
}
catch (std::exception ex)
{
Logger::warn(L"An exception occurred while loading the settings file");
// Error while loading from the settings file. Let default values stay as they are.
}
}
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new Espresso();
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200729.8" targetFramework="native" />
</packages>

View file

@ -0,0 +1 @@
#include "pch.h"

View file

@ -0,0 +1,7 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <ProjectTelemetry.h>
#include <shellapi.h>
#include <Shlwapi.h>

View file

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Espresso.rc
//
#define IDS_ESPRESSO_NAME 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View file

@ -0,0 +1,19 @@
#include "pch.h"
#include "trace.h"
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::RegisterProvider()
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider()
{
TraceLoggingUnregister(g_hProvider);
}

View file

@ -0,0 +1,8 @@
#pragma once
class Trace
{
public:
static void RegisterProvider();
static void UnregisterProvider();
};

View file

@ -148,7 +148,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe)
chdir_current_executable();
// Load Powertoys DLLs
const std::array<std::wstring_view, 8> knownModules = {
const std::array<std::wstring_view, 9> knownModules = {
L"modules/FancyZones/fancyzones.dll",
L"modules/FileExplorerPreview/powerpreview.dll",
L"modules/ImageResizer/ImageResizerExt.dll",
@ -157,6 +157,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe)
L"modules/PowerRename/PowerRenameExt.dll",
L"modules/ShortcutGuide/ShortcutGuide.dll",
L"modules/ColorPicker/ColorPicker.dll",
L"modules/Espresso/EspressoModuleInterface.dll",
};
for (const auto& moduleSubdir : knownModules)

View file

@ -143,6 +143,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool espresso = true;
[JsonPropertyName("Espresso")]
public bool Espresso
{
get => espresso;
set
{
if (espresso != value)
{
LogTelemetryEvent(value);
espresso = value;
}
}
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);

View file

@ -0,0 +1,37 @@
// 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.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class EspressoProperties
{
public EspressoProperties()
{
KeepDisplayOn = false;
Mode = EspressoMode.INDEFINITE;
Hours = 0;
Minutes = 0;
}
[JsonPropertyName("espresso_keep_display_on")]
public bool KeepDisplayOn { get; set; }
[JsonPropertyName("espresso_mode")]
public EspressoMode Mode { get; set; }
[JsonPropertyName("espresso_hours")]
public uint Hours { get; set; }
[JsonPropertyName("espresso_minutes")]
public uint Minutes { get; set; }
}
public enum EspressoMode
{
INDEFINITE = 0,
TIMED = 1,
}
}

View file

@ -0,0 +1,35 @@
// 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.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class EspressoSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "Espresso";
public const string ModuleVersion = "1.0.0";
public EspressoSettings()
{
Name = ModuleName;
Version = ModuleVersion;
Properties = new EspressoProperties();
}
[JsonPropertyName("properties")]
public EspressoProperties Properties { get; set; }
public string GetModuleName()
{
return Name;
}
public bool UpgradeSettingsConfiguration()
{
return false;
}
}
}

View file

@ -19,5 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
bool SettingsExists(string powertoy = "", string fileName = "settings.json");
void DeleteSettings(string powertoy = "");
string GetSettingsFilePath(string powertoy = "", string fileName = "settings.json");
}
}

View file

@ -20,6 +20,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

View file

@ -135,5 +135,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
#endif
}
}
// Returns the file path to the settings file, that is exposed from the local ISettingsPath instance.
public string GetSettingsFilePath(string powertoy = "", string fileName = "settings.json")
{
return _settingsPath.GetSettingsPath(powertoy, fileName);
}
}
}

View file

@ -0,0 +1,32 @@
// 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.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class SndEspressoSettings
{
[JsonPropertyName("Espresso")]
public EspressoSettings Settings { get; set; }
public SndEspressoSettings()
{
}
public SndEspressoSettings(EspressoSettings settings)
{
Settings = settings;
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View file

@ -0,0 +1,148 @@
// 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.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
{
public class EspressoViewModel : Observable
{
private GeneralSettings GeneralSettingsConfig { get; set; }
private EspressoSettings Settings { get; set; }
private Func<string, int> SendConfigMSG { get; }
public EspressoViewModel(ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<EspressoSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
{
// To obtain the general settings configurations of PowerToys Settings.
if (settingsRepository == null)
{
throw new ArgumentNullException(nameof(settingsRepository));
}
GeneralSettingsConfig = settingsRepository.SettingsConfig;
// To obtain the settings configurations of Fancy zones.
if (moduleSettingsRepository == null)
{
throw new ArgumentNullException(nameof(moduleSettingsRepository));
}
Settings = moduleSettingsRepository.SettingsConfig;
_isEnabled = GeneralSettingsConfig.Enabled.Espresso;
_keepDisplayOn = Settings.Properties.KeepDisplayOn;
_mode = Settings.Properties.Mode;
_hours = Settings.Properties.Hours;
_minutes = Settings.Properties.Minutes;
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
}
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_isEnabled != value)
{
_isEnabled = value;
GeneralSettingsConfig.Enabled.Espresso = value;
var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
NotifyPropertyChanged();
}
}
}
public EspressoMode Mode
{
get => _mode;
set
{
if (_mode != value)
{
_mode = value;
OnPropertyChanged(nameof(Mode));
Settings.Properties.Mode = value;
NotifyPropertyChanged();
}
}
}
public bool KeepDisplayOn
{
get => _keepDisplayOn;
set
{
if (_keepDisplayOn != value)
{
_keepDisplayOn = value;
OnPropertyChanged(nameof(KeepDisplayOn));
Settings.Properties.KeepDisplayOn = value;
}
}
}
public uint Hours
{
get => _hours;
set
{
if (_hours != value)
{
_hours = value;
OnPropertyChanged(nameof(Hours));
Settings.Properties.Hours = value;
NotifyPropertyChanged();
}
}
}
public uint Minutes
{
get => _minutes;
set
{
if (_minutes != value)
{
_minutes = value;
OnPropertyChanged(nameof(Minutes));
Settings.Properties.Minutes = value;
NotifyPropertyChanged();
}
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
if (SendConfigMSG != null)
{
SndEspressoSettings outsettings = new SndEspressoSettings(Settings);
SndModuleSettings<SndEspressoSettings> ipcMessage = new SndModuleSettings<SndEspressoSettings>(outsettings);
var targetMessage = ipcMessage.ToJsonString();
SendConfigMSG(targetMessage);
}
}
private bool _isEnabled;
private uint _hours;
private uint _minutes;
private bool _keepDisplayOn;
private EspressoMode _mode;
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
@ -9,6 +9,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutputPath>..\..\..\x64\Debug\SettingsTests\</OutputPath>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,44 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class EspressoModeToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (targetType != typeof(bool))
{
throw new InvalidOperationException("The target type needs to be a boolean.");
}
switch ((EspressoMode)value)
{
case EspressoMode.INDEFINITE:
return true;
case EspressoMode.TIMED:
return false;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
switch ((bool)value)
{
case true:
return EspressoMode.INDEFINITE;
case false:
return EspressoMode.TIMED;
}
return EspressoMode.INDEFINITE;
}
}
}

View file

@ -0,0 +1,44 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class EspressoModeToReverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (targetType != typeof(bool))
{
throw new InvalidOperationException("The target type needs to be a boolean.");
}
switch ((EspressoMode)value)
{
case EspressoMode.INDEFINITE:
return false;
case EspressoMode.TIMED:
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
switch ((bool)value)
{
case false:
return EspressoMode.INDEFINITE;
case true:
return EspressoMode.TIMED;
}
return EspressoMode.INDEFINITE;
}
}
}

View file

@ -105,6 +105,8 @@
<Compile Include="Controls\ShortcutVisualControl.xaml.cs">
<DependentUpon>ShortcutVisualControl.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\EspressoModeToBoolConverter.cs" />
<Compile Include="Converters\EspressoModeToReverseBoolConverter.cs" />
<Compile Include="Converters\ModuleEnabledToForegroundConverter.cs" />
<Compile Include="Generated Files\AssemblyInfo.cs">
<SubType>Code</SubType>
@ -121,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>
@ -158,6 +163,9 @@
<Compile Include="Services\NavigationService.cs" />
<Compile Include="ViewModels\Commands\ButtonClickCommand.cs" />
<Compile Include="ViewModels\ShellViewModel.cs" />
<Compile Include="Views\EspressoPage.xaml.cs">
<DependentUpon>EspressoPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ColorPickerPage.xaml.cs">
<DependentUpon>ColorPickerPage.xaml</DependentUpon>
</Compile>
@ -197,6 +205,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="Assets\FluentIcons\FluentIconsColorPicker.png" />
<Content Include="Assets\FluentIcons\FluentIconsEspresso.png" />
<Content Include="Assets\FluentIcons\FluentIconsFancyZones.png" />
<Content Include="Assets\FluentIcons\FluentIconsFileExplorerPreview.png" />
<Content Include="Assets\FluentIcons\FluentIconsImageResizer.png" />
@ -275,7 +284,7 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<PRIResource Include="Strings\*\Resources.resw" />
<PRIResource Include="Strings\en-us\Resources.resw" />
</ItemGroup>
<ItemGroup>
<Page Include="Controls\HotkeySettingsControl.xaml">
@ -294,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>
@ -366,6 +379,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\EspressoPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\ColorPickerPage.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

@ -121,6 +121,10 @@
<value>General</value>
<comment>Navigation view item name for General</comment>
</data>
<data name="Shell_Espresso.Content" xml:space="preserve">
<value>Espresso</value>
<comment>Product name: Navigation view item name for Espresso</comment>
</data>
<data name="Shell_PowerLauncher.Content" xml:space="preserve">
<value>PowerToys Run</value>
<comment>Product name: Navigation view item name for PowerToys Run</comment>
@ -905,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>
@ -1161,38 +1169,60 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and
<data name="SettingsWindow_Title" xml:space="preserve">
<value>PowerToys Settings</value>
</data>
<data name="Run_PositionAppearance_GroupSettings.Text" xml:space="preserve">
<value>Position &amp; appearance</value>
<data name="About_Espresso.Text" xml:space="preserve">
<value>About Espresso</value>
</data>
<data name="Run_PositionHeader.Text" xml:space="preserve">
<value>Show PowerToys Run on</value>
<comment>as in Show PowerToys Run on primary monitor</comment>
<data name="Espresso_Description.Text" xml:space="preserve">
<value>A convenient way to keep your computer awake on-demand.</value>
</data>
<data name="Run_Radio_Position_Cursor.Content" xml:space="preserve">
<value>Monitor with mouse cursor</value>
<data name="Espresso_EnableEspresso.Header" xml:space="preserve">
<value>Enable Espresso</value>
</data>
<data name="Run_Radio_Position_Focus.Content" xml:space="preserve">
<value>Monitor with focused window</value>
<data name="Espresso_IndefiniteKeepAwakeContent.Text" xml:space="preserve">
<value>Keep awake indefinitely</value>
</data>
<data name="Run_Radio_Position_Primary_Monitor.Content" xml:space="preserve">
<value>Primary monitor</value>
<data name="Espresso_TemporaryKeepAwakeContent.Text" xml:space="preserve">
<value>Keep awake temporarily</value>
</data>
<data name="Run_PluginsLoading.Text" xml:space="preserve">
<value>Plugins are loading...</value>
<data name="Espresso_IndefiniteKeepAwakeDescription.Text" xml:space="preserve">
<value>Keeps the computer awake until the setting is disabled.</value>
</data>
<data name="ColorPicker_ButtonDown.AutomationProperties.Name" xml:space="preserve">
<value>Move the color down</value>
<data name="Espresso_TemporaryKeepAwakeDescription.Text" xml:space="preserve">
<value>Keeps the computer awake until the set time elapses.</value>
</data>
<data name="ColorPicker_ButtonUp.AutomationProperties.Name" xml:space="preserve">
<value>Move the color up</value>
<data name="Espresso_EnableDisplayKeepAwake.Content" xml:space="preserve">
<value>Keep screen on</value>
</data>
<data name="FancyZones_FlashZonesOnQuickSwitch.Content" xml:space="preserve">
<value>Flash zones when switching layout</value>
<data name="Espresso_Mode.Text" xml:space="preserve">
<value>Mode</value>
</data>
<data name="FancyZones_QuickLayoutSwitch.Content" xml:space="preserve">
<value>Enable quick layout switch</value>
<data name="Espresso_Behavior_GroupSettings.Text" xml:space="preserve">
<value>Behavior</value>
</data>
<data name="FancyZones_QuickLayoutSwitch_GroupSettings.Text" xml:space="preserve">
<value>Quick layout switch</value>
<data name="Espresso_TemporaryKeepAwake_Hours.Header" xml:space="preserve">
<value>Hours</value>
</data>
</root>
<data name="Espresso_TemporaryKeepAwake_Minutes.Header" xml:space="preserve">
<value>Minutes</value>
</data>
<data name="Espresso_ModuleAttributionLabel.Text" xml:space="preserve">
<value>Den Delimarsky's Espresso</value>
</data>
<data name="Espresso_ModuleAttributionHyperlink.NavigateUri" xml:space="preserve">
<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>

View file

@ -0,0 +1,188 @@
<Page
x:Class="Microsoft.PowerToys.Settings.UI.Views.EspressoPage"
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.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:c="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:Custom="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:Color="using:Microsoft.PowerToys.Settings.UI.Library.ViewModels" xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
AutomationProperties.LandmarkType="Main">
<Page.Resources>
<c:EspressoModeToBoolConverter x:Key="EspressoModeToBoolConverter" />
<c:EspressoModeToReverseBoolConverter x:Key="EspressoModeToReverseBoolConverter" />
</Page.Resources>
<Grid RowSpacing="{StaticResource DefaultRowSpacing}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutVisualStates">
<VisualState x:Name="WideLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource WideLayoutMinWidth}" />
</VisualState.StateTriggers>
</VisualState>
<VisualState x:Name="SmallLayout">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource SmallLayoutMinWidth}" />
<AdaptiveTrigger MinWindowWidth="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="SidePanel.(Grid.Column)" Value="0" />
<Setter Target="SidePanel.Width" Value="Auto" />
<Setter Target="ColorPickerView.(Grid.Row)" Value="1" />
<Setter Target="ColorPickerView.Margin" Value="0" />
<Setter Target="LinksPanel.(RelativePanel.RightOf)" Value="AboutImage" />
<Setter Target="LinksPanel.(RelativePanel.AlignTopWith)" Value="AboutImage" />
<Setter Target="AboutImage.Margin" Value="0,12,12,0" />
<Setter Target="AboutTitle.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical"
x:Name="EspressoView"
HorizontalAlignment="Left"
Margin="0,0,48,0"
MaxWidth="{StaticResource MaxContentWidth}">
<ToggleSwitch x:Uid="Espresso_EnableEspresso" IsOn="{Binding IsEnabled, Mode=TwoWay}" />
<!--IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.EnablePowerLauncher}"/>-->
<!--<TextBlock x:Uid="Shortcuts"
Style="{StaticResource SettingsGroupTitleStyle}"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.EnablePowerLauncher, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>-->
<TextBlock x:Uid="Espresso_Behavior_GroupSettings"
Style="{StaticResource SettingsGroupTitleStyle}"/>
<CheckBox x:Uid="Espresso_EnableDisplayKeepAwake"
IsEnabled="{Binding IsEnabled}"
IsChecked="{Binding KeepDisplayOn, Mode=TwoWay}"
Margin="{StaticResource XSmallTopMargin}" />
<TextBlock x:Uid="Espresso_Mode"
Margin="{StaticResource SmallTopMargin}"
x:Name="ModeTitleLabel"
Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}" />
<StackPanel AutomationProperties.LabeledBy="{Binding ElementName=ModeTitleLabel}"
Margin="0,-4,0,0">
<RadioButton x:Uid="Espresso_IndefiniteKeepAwake"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}"
IsChecked="{Binding Mode, Mode=TwoWay, Converter={StaticResource EspressoModeToBoolConverter}}">
<RadioButton.Content>
<TextBlock TextWrapping="WrapWholeWords" LineHeight="20">
<Run x:Uid="Espresso_IndefiniteKeepAwakeContent"/>
<LineBreak/>
<Run Foreground="{ThemeResource SystemBaseMediumColor}"
x:Uid="Espresso_IndefiniteKeepAwakeDescription"/>
</TextBlock>
</RadioButton.Content>
</RadioButton>
<RadioButton x:Uid="Espresso_TemporaryKeepAwake"
Margin="{StaticResource SmallTopMargin}"
IsEnabled="{Binding IsEnabled}"
IsChecked="{Binding Mode, Mode=TwoWay, Converter={StaticResource EspressoModeToReverseBoolConverter}}">
<RadioButton.Content>
<TextBlock TextWrapping="WrapWholeWords" LineHeight="20">
<Run x:Uid="Espresso_TemporaryKeepAwakeContent"/>
<LineBreak/>
<Run Foreground="{ThemeResource SystemBaseMediumColor}"
x:Uid="Espresso_TemporaryKeepAwakeDescription"/>
</TextBlock>
</RadioButton.Content>
</RadioButton>
<StackPanel Margin="28,8,0,0" Orientation="Horizontal">
<muxc:NumberBox x:Uid="Espresso_TemporaryKeepAwake_Hours"
Value="{Binding Hours, Mode=TwoWay}"
Minimum="0"
SpinButtonPlacementMode="Compact"
HorizontalAlignment="Left"
MinWidth="90"
IsEnabled="{Binding Mode, Converter={StaticResource EspressoModeToReverseBoolConverter}}"
SmallChange="1"
LargeChange="5"/>
<muxc:NumberBox x:Uid="Espresso_TemporaryKeepAwake_Minutes"
Value="{Binding Minutes, Mode=TwoWay}"
Minimum="1"
SpinButtonPlacementMode="Compact"
Margin="8,0,0,0"
HorizontalAlignment="Left"
MinWidth="90"
IsEnabled="{Binding Mode, Converter={StaticResource EspressoModeToReverseBoolConverter}}"
SmallChange="1"
LargeChange="5"/>
</StackPanel>
</StackPanel>
<!--Foreground="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"-->
</StackPanel>
<RelativePanel x:Name="SidePanel"
HorizontalAlignment="Left"
Width="{StaticResource SidePanelWidth}"
Grid.Column="1">
<StackPanel x:Name="DescriptionPanel">
<TextBlock x:Uid="About_Espresso"
x:Name="AboutTitle"
Grid.ColumnSpan="2"
Style="{StaticResource SettingsGroupTitleStyle}"
Margin="{StaticResource XSmallBottomMargin}"/>
<TextBlock x:Uid="Espresso_Description"
TextWrapping="Wrap"
Grid.Row="1" />
</StackPanel>
<Border x:Name="AboutImage"
CornerRadius="4"
Grid.Row="2"
MaxWidth="240"
HorizontalAlignment="Left"
Margin="{StaticResource SmallTopBottomMargin}"
RelativePanel.Below="DescriptionPanel">
<HyperlinkButton x:Uid="Espresso_ImageHyperlinkToDocs">
<Image x:Uid="Espresso_Image" Source="ms-appx:///Assets/Modules/ColorPicker.png" />
</HyperlinkButton>
</Border>
<StackPanel x:Name="LinksPanel"
Margin="0,1,0,0"
RelativePanel.Below="AboutImage"
Orientation="Vertical" >
<HyperlinkButton x:Uid="Espresso_ImageHyperlinkToDocs">
<TextBlock x:Uid="Module_overview" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="https://aka.ms/powerToysGiveFeedback">
<TextBlock x:Uid="Give_Feedback" />
</HyperlinkButton>
<TextBlock
x:Uid="AttributionTitle"
Style="{StaticResource SettingsGroupTitleStyle}" />
<HyperlinkButton Margin="0,-3,0,0"
x:Uid="Espresso_ModuleAttributionHyperlink">
<TextBlock x:Uid="Espresso_ModuleAttributionLabel"
TextWrapping="Wrap" />
</HyperlinkButton>
</StackPanel>
</RelativePanel>
</Grid>
</Page>

View file

@ -0,0 +1,23 @@
// 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 Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels;
using Windows.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Views
{
public sealed partial class EspressoPage : Page
{
private EspressoViewModel ViewModel { get; set; }
public EspressoPage()
{
var settingsUtils = new SettingsUtils();
ViewModel = new EspressoViewModel(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<EspressoSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
}
}
}

View file

@ -41,7 +41,13 @@
<FontIcon Glyph="&#xEF3C;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<winui:NavigationViewItem x:Uid="Shell_Espresso" helpers:NavHelper.NavigateTo="views:EspressoPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<FontIcon Glyph="&#xEC32;"/>
</winui:NavigationViewItem.Icon>
</winui:NavigationViewItem>
<winui:NavigationViewItem x:Uid="Shell_FancyZones" helpers:NavHelper.NavigateTo="views:FancyZonesPage" AutomationProperties.HeadingLevel="Level1">
<winui:NavigationViewItem.Icon>
<PathIcon Data="M45,48H25.5V45H45V25.5H25.5v-3H45V3H25.5V0H48V48ZM22.5,48H3V45H22.5V3H3V0H25.5V48ZM0,48V0H3V48Z"/>

View file

@ -34,6 +34,9 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutputPath>..\..\..\$(Platform)\$(Configuration)\Settings</OutputPath>
<Optimize>false</Optimize>
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">