Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade

This commit is contained in:
Jeremy Sinclair 2023-10-21 09:30:17 -04:00
commit b08cd1a236
126 changed files with 6717 additions and 85 deletions

View File

@ -789,6 +789,7 @@ iid
Iindex
IIO
iiq
IJson
Ijwhost
IKs
ILogon
@ -914,6 +915,7 @@ Lastdevice
Laute
laviusmotileng
LAYOUTRTL
Lbl
LBUTTON
LBUTTONDBLCLK
LBUTTONDOWN
@ -1342,6 +1344,7 @@ PARTIALCONFIRMATIONDIALOGTITLE
pasteplain
PATCOPY
pathcch
PATHEXT
Pathto
PATINVERT
PATPAINT
@ -1469,6 +1472,7 @@ pscid
PSECURITY
psfgao
psfi
PSMODULEPATH
Psr
psrm
psrree
@ -1691,6 +1695,7 @@ setlocal
SETREDRAW
SETTEXT
SETTINGCHANGE
SETTINGSCHANGED
settingsheader
settingshotkeycontrol
SETWORKAREA

View File

@ -98,6 +98,10 @@
"WinUI3Apps\\Powertoys.Peek.UI.exe",
"WinUI3Apps\\Powertoys.Peek.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariables.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariables.exe",
"PowerToys.ImageResizer.exe",
"PowerToys.ImageResizer.dll",
"PowerToys.ImageResizerExt.dll",

View File

@ -8,6 +8,7 @@
<PackageVersion Include="CommunityToolkit.WinUI.Animations" Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Collections " Version="8.0.230907" />
<PackageVersion Include="CommunityToolkit.WinUI.Converters" Version="8.0.230907" />

View File

@ -312,6 +312,7 @@ SOFTWARE.
- CommunityToolkit.Mvvm 8.2.0
- CommunityToolkit.WinUI.Animations 8.0.230907
- CommunityToolkit.WinUI.Controls.Primitives 8.0.230907
- CommunityToolkit.WinUI.Controls.Segmented 8.0.230907
- CommunityToolkit.WinUI.Controls.SettingsControls 8.0.230907
- CommunityToolkit.WinUI.Controls.Sizers 8.0.230907
- CommunityToolkit.WinUI.Converters 8.0.230907

View File

@ -539,6 +539,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvironmentVariables", "EnvironmentVariables", "{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariables", "src\modules\EnvironmentVariables\EnvironmentVariables\EnvironmentVariables.csproj", "{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnvironmentVariablesModuleInterface", "src\modules\EnvironmentVariables\EnvironmentVariablesModuleInterface\EnvironmentVariablesModuleInterface.vcxproj", "{B9420661-B0E4-4241-ABD4-4A27A1F64250}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@ -2321,6 +2327,30 @@ Global
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Release|x64.Build.0 = Release|x64
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Release|x86.ActiveCfg = Release|x64
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}.Release|x86.Build.0 = Release|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|ARM64.ActiveCfg = Debug|ARM64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|ARM64.Build.0 = Debug|ARM64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x64.ActiveCfg = Debug|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x64.Build.0 = Debug|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x86.ActiveCfg = Debug|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Debug|x86.Build.0 = Debug|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|ARM64.ActiveCfg = Release|ARM64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|ARM64.Build.0 = Release|ARM64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x64.ActiveCfg = Release|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x64.Build.0 = Release|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x86.ActiveCfg = Release|x64
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA}.Release|x86.Build.0 = Release|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|ARM64.Build.0 = Debug|ARM64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x64.ActiveCfg = Debug|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x64.Build.0 = Debug|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x86.ActiveCfg = Debug|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Debug|x86.Build.0 = Debug|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|ARM64.ActiveCfg = Release|ARM64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|ARM64.Build.0 = Release|ARM64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.ActiveCfg = Release|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.Build.0 = Release|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.ActiveCfg = Release|x64
{B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2516,6 +2546,9 @@ Global
{F5E1146E-B7B3-4E11-85FD-270A500BD78C} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
{3157FA75-86CF-4EE2-8F62-C43F776493C6} = {3B227528-4BA6-4CAF-B44A-A10C78A64849}
{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
{B9420661-B0E4-4241-ABD4-4A27A1F64250} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" >
<?include $(sys.CURRENTDIR)\Common.wxi?>
<?define EnvironmentVariablesAssetsFiles=?>
<?define EnvironmentVariablesAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\EnvironmentVariables\?>
<Fragment>
<DirectoryRef Id="WinUI3AppsAssetsFolder">
<Directory Id="EnvironmentVariablesAssetsFolder" Name="EnvironmentVariables" />
</DirectoryRef>
<DirectoryRef Id="EnvironmentVariablesAssetsFolder" FileSource="$(var.EnvironmentVariablesAssetsFilesPath)">
<!-- Generated by generateFileComponents.ps1 -->
<!--EnvironmentVariablesAssetsFiles_Component_Def-->
</DirectoryRef>
<ComponentGroup Id="EnvironmentVariablesComponentGroup">
<Component Id="RemoveEnvironmentVariablesFolder" Guid="B62A779D-38BA-46B2-859D-9D242D9B0CC1" Directory="EnvironmentVariablesAssetsFolder" >
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveEnvironmentVariablesFolder" Value="" KeyPath="yes"/>
</RegistryKey>
<RemoveFolder Id="RemoveFolderEnvironmentVariablesAssetsFolder" Directory="EnvironmentVariablesAssetsFolder" On="uninstall"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@ -35,6 +35,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
call move /Y ..\..\..\BaseApplications.wxs.bk ..\..\..\BaseApplications.wxs
call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs
call move /Y ..\..\..\Core.wxs.bk ..\..\..\Core.wxs
call move /Y ..\..\..\EnvironmentVariables.wxs.bk ..\..\..\EnvironmentVariables.wxs
call move /Y ..\..\..\FileExplorerPreview.wxs.bk ..\..\..\FileExplorerPreview.wxs
call move /Y ..\..\..\FileLocksmith.wxs.bk ..\..\..\FileLocksmith.wxs
call move /Y ..\..\..\Hosts.wxs.bk ..\..\..\Hosts.wxs
@ -104,6 +105,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="Awake.wxs" />
<Compile Include="BaseApplications.wxs" />
<Compile Include="ColorPicker.wxs" />
<Compile Include="EnvironmentVariables.wxs" />
<Compile Include="FileExplorerPreview.wxs" />
<Compile Include="FileLocksmith.wxs" />
<Compile Include="Hosts.wxs" />

View File

@ -71,6 +71,7 @@
<ComponentGroupRef Id="ShortcutGuideComponentGroup" />
<ComponentGroupRef Id="VideoConferenceComponentGroup" />
<ComponentGroupRef Id="MouseWithoutBordersComponentGroup" />
<ComponentGroupRef Id="EnvironmentVariablesComponentGroup" />
<ComponentGroupRef Id="ResourcesComponentGroup" />
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />

View File

@ -32,6 +32,10 @@ Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListNa
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName ColorPickerAssetsFiles -wxsFilePath $PSScriptRoot\ColorPicker.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\Assets\ColorPicker"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""ColorPickerAssetsFiles"" -wxsFilePath $PSScriptRoot\ColorPicker.wxs -regroot $registryroot"
#Environment Variables
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName EnvironmentVariablesAssetsFiles -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\EnvironmentVariables"""
Invoke-Expression -Command "$PSScriptRoot\generateFileComponents.ps1 -fileListName ""EnvironmentVariablesAssetsFiles"" -wxsFilePath $PSScriptRoot\EnvironmentVariables.wxs -regroot $registryroot"
#FileExplorerAdd-ons
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName MonacoPreviewHandlerMonacoAssetsFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco"""
Invoke-Expression -Command "$PSScriptRoot\generateFileList.ps1 -fileDepsJson """" -fileListName MonacoPreviewHandlerCustomLanguagesFiles -wxsFilePath $PSScriptRoot\FileExplorerPreview.wxs -depsPath ""$PSScriptRoot..\..\..\$platform\Release\Assets\Monaco\customLanguages"""

View File

@ -1005,7 +1005,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
}
processes.resize(bytes / sizeof(processes[0]));
std::array<std::wstring_view, 28> processesToTerminate = {
std::array<std::wstring_view, 29> processesToTerminate = {
L"PowerToys.PowerLauncher.exe",
L"PowerToys.Settings.exe",
L"PowerToys.Awake.exe",
@ -1033,6 +1033,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
L"PowerToys.MouseWithoutBordersHelper.exe",
L"PowerToys.MouseWithoutBordersService.exe",
L"PowerToys.CropAndLock.exe",
L"PowerToys.EnvironmentVariables.exe",
L"PowerToys.exe",
};

View File

@ -52,6 +52,7 @@
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs"" ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Core.wxs"" ""$(ProjectDir)..\PowerToysSetup\Core.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs"" ""$(ProjectDir)..\PowerToysSetup\EnvironmentVariables.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileExplorerPreview.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs"" ""$(ProjectDir)..\PowerToysSetup\FileLocksmith.wxs.bk""""
call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs"" ""$(ProjectDir)..\PowerToysSetup\Hosts.wxs.bk""""

View File

@ -33,7 +33,7 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Static methods may improve performance but decrease maintainability")]
[assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Renaming everything would be a lot of work. It does not do any harm if an EventHandler delegate ends with the suffix EventHandler. Besides this, the Rule causes some false positives.")]
[assembly: SuppressMessage("Performance", "CA1838:Avoid 'StringBuilder' parameters for P/Invokes", Justification = "We are not concerned about the performance impact of marshaling a StringBuilder")]
[assembly: SuppressMessage("Performance", "CA1852:Seal internal types", Justification = "The assembly is getting a ComVisible set to false already.", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("Performance", "CA1852:Seal internal types", Justification = "The assembly is getting a ComVisible set to false already.", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]
// Threading suppressions
[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.Controls.Notification.OnClose")]
@ -57,8 +57,8 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("CodeQuality", "IDE0076:Invalid global 'SuppressMessageAttribute'", Justification = "Affect predefined suppressions.")]
// Dotnet port
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "<Dotnet port with style preservation>", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "<Dotnet port with style preservation>", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "<Dotnet port with style preservation>", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "<Dotnet port with style preservation>", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "<Dotnet port with style preservation>", Scope="namespaceanddescendants", Target="MouseWithoutBorders")]
[assembly: SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "<Dotnet port with style preservation>", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "<Dotnet port with style preservation>", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "<Dotnet port with style preservation>", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "<Dotnet port with style preservation>", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "<Dotnet port with style preservation>", Scope = "namespaceanddescendants", Target = "MouseWithoutBorders")]

View File

@ -28,6 +28,8 @@ namespace Common.UI
PowerOCR,
RegistryPreview,
CropAndLock,
EnvironmentVariables,
Dashboard,
}
private static string SettingsWindowNameToString(SettingsWindow value)
@ -68,6 +70,10 @@ namespace Common.UI
return "RegistryPreview";
case SettingsWindow.CropAndLock:
return "CropAndLock";
case SettingsWindow.EnvironmentVariables:
return "EnvironmentVariables";
case SettingsWindow.Dashboard:
return "Dashboard";
default:
{
return string.Empty;

View File

@ -148,4 +148,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getRunPluginEnabledValue(winrt::to_string(pluginID)));
}
GpoRuleConfigured GPOWrapper::GetConfiguredEnvironmentVariablesEnabledValue()
{
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredEnvironmentVariablesEnabledValue());
}
}

View File

@ -43,6 +43,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue();
static GpoRuleConfigured GetAllowExperimentationValue();
static GpoRuleConfigured GetRunPluginEnabledValue(winrt::hstring const& pluginID);
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
};
}

View File

@ -47,6 +47,7 @@ namespace PowerToys
static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue();
static GpoRuleConfigured GetAllowExperimentationValue();
static GpoRuleConfigured GetRunPluginEnabledValue(String pluginID);
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
}
}
}

View File

@ -266,5 +266,13 @@ public
static String ^ CropAndLockReparentEvent() {
return gcnew String(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
}
static String ^ ShowEnvironmentVariablesSharedEvent() {
return gcnew String(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT);
}
static String ^ ShowEnvironmentVariablesAdminSharedEvent() {
return gcnew String(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT);
}
};
}

View File

@ -82,6 +82,10 @@ namespace CommonSharedConstants
const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a";
// Path to the events used by EnvironmentVariables
const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978";
const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2";
// Max DWORD for key code to disable keys.
const DWORD VK_DISABLED = 0x100;
}

View File

@ -62,6 +62,7 @@ struct LogSettings
inline const static std::string registryPreviewLoggerName = "registrypreview";
inline const static std::string cropAndLockLoggerName = "crop-and-lock";
inline const static std::wstring registryPreviewLogPath = L"Logs\\registryPreview-log.txt";
inline const static std::string environmentVariablesLoggerName = "environment-variables";
inline const static int retention = 30;
std::wstring logLevel;
LogSettings();

View File

@ -55,6 +55,7 @@ namespace powertoys_gpo {
const std::wstring POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW = L"ConfigureEnabledUtilityRegistryPreview";
const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS = L"ConfigureEnabledUtilityMouseWithoutBorders";
const std::wstring POLICY_CONFIGURE_ENABLED_PEEK = L"ConfigureEnabledUtilityPeek";
const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables";
// The registry value names for PowerToys installer and update policies.
const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled";
@ -376,6 +377,11 @@ namespace powertoys_gpo {
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW);
}
inline gpo_rule_configured_t getConfiguredEnvironmentVariablesEnabledValue()
{
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES);
}
inline gpo_rule_configured_t getDisablePerUserInstallationValue()
{
return getConfiguredValue(POLICY_DISABLE_PER_USER_INSTALLATION);
@ -440,5 +446,4 @@ namespace powertoys_gpo {
return getConfiguredValue(POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,114 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Version.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.20348.0</TargetFramework>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<RootNamespace>EnvironmentVariables</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<RuntimeIdentifiers>win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<AssemblyName>PowerToys.EnvironmentVariables</AssemblyName>
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
<ApplicationIcon>Assets/EnvironmentVariables/EnvironmentVariables.ico</ApplicationIcon>
<SelfContained>true</SelfContained>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.EnvironmentVariables.pri</ProjectPriFileName>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Styles\**" />
<EmbeddedResource Remove="Styles\**" />
<None Remove="Styles\**" />
<Page Remove="Styles\**" />
</ItemGroup>
<!-- SelfContained=true requires RuntimeIdentifier to be set -->
<PropertyGroup Condition="'$(Platform)'=='x64'">
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(Platform)'=='ARM64'">
<RuntimeIdentifier>win10-arm64</RuntimeIdentifier>
</PropertyGroup>
<!-- See https://learn.microsoft.com/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component for more info -->
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<None Remove="EnvironmentVariablesXAML\Views\MainPage.xaml" />
</ItemGroup>
<ItemGroup>
<Page Remove="EnvironmentVariablesXAML\App.xaml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="EnvironmentVariablesXAML\App.xaml" />
</ItemGroup>
<!-- Needed for CommunityToolkit.Labs.WinUI.SettingsControls and Needed for CommunityToolkit.Labs.WinUI.SegmentedControl. -->
<PropertyGroup>
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\EnvironmentVariables\SplashScreen.scale-200.png" />
<Content Include="Assets\EnvironmentVariables\LockScreenLogo.scale-200.png" />
<Content Include="Assets\EnvironmentVariables\Square150x150Logo.scale-200.png" />
<Content Include="Assets\EnvironmentVariables\Square44x44Logo.scale-200.png" />
<Content Include="Assets\EnvironmentVariables\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\EnvironmentVariables\StoreLogo.png" />
<Content Include="Assets\EnvironmentVariables\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="WinUIEx" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<!-- HACK: Common.UI is referenced, even if it is not used, to force dll versions to be the same as in other projects that use it. It's still unclear why this is the case, but this is need for flattening the install directory. -->
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="EnvironmentVariablesXAML\Views\MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<PRIResource Remove="Styles\**" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8" ?>
<Application
x:Class="EnvironmentVariables.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:EnvironmentVariables">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/EnvironmentVariablesXAML/Styles/TextBlock.xaml" />
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="SubtleButtonBackground" Color="{ThemeResource SubtleFillColorTransparent}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundPointerOver" Color="{ThemeResource SubtleFillColorSecondary}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundPressed" Color="{ThemeResource SubtleFillColorTertiary}" />
<SolidColorBrush x:Key="SubtleButtonBackgroundDisabled" Color="{ThemeResource ControlFillColorDisabled}" />
<SolidColorBrush x:Key="SubtleButtonForeground" Color="{ThemeResource TextFillColorPrimary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundPointerOver" Color="{ThemeResource TextFillColorPrimary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundPressed" Color="{ThemeResource TextFillColorSecondary}" />
<SolidColorBrush x:Key="SubtleButtonForegroundDisabled" Color="{ThemeResource TextFillColorDisabled}" />
<Style x:Key="SubtleButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{ThemeResource SubtleButtonBackground}" />
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
<Setter Property="Foreground" Value="{ThemeResource SubtleButtonForeground}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
<Setter Property="FocusVisualMargin" Value="-3" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AnimatedIcon.State="Normal"
AutomationProperties.AccessibilityView="Raw"
Background="{TemplateBinding Background}"
BackgroundSizing="{TemplateBinding BackgroundSizing}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CornerRadius="{TemplateBinding CornerRadius}">
<ContentPresenter.BackgroundTransition>
<BrushTransition Duration="0:0:0.083" />
</ContentPresenter.BackgroundTransition>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SubtleButtonForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<VisualState.Setters>
<!-- DisabledVisual Should be handled by the control, not the animated icon. -->
<Setter Target="ContentPresenter.(AnimatedIcon.State)" Value="Normal" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ContentPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,96 @@
// 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.Abstractions;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.ViewModels;
using ManagedCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
namespace EnvironmentVariables
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
public IHost Host { get; }
public static T GetService<T>()
where T : class
{
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) =>
{
services.AddSingleton<IFileSystem, FileSystem>();
services.AddSingleton<IElevationHelper, ElevationHelper>();
services.AddSingleton<IEnvironmentVariablesService, EnvironmentVariablesService>();
services.AddSingleton<MainViewModel>();
}).Build();
UnhandledException += App_UnhandledException;
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
Logger.LogError("Unhandled exception", e.Exception);
}
/// <summary>
/// Invoked when the application is launched.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs?.Length > 1)
{
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
{
Logger.LogInfo($"EnvironmentVariables started from the PowerToys Runner. Runner pid={powerToysRunnerPid}.");
var dispatcher = DispatcherQueue.GetForCurrentThread();
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
{
Logger.LogInfo("PowerToys Runner exited. Exiting EnvironmentVariables");
dispatcher.TryEnqueue(App.Current.Exit);
});
}
}
else
{
Logger.LogInfo($"EnvironmentVariables started detached from PowerToys Runner.");
}
PowerToysTelemetry.Log.WriteEvent(new EnvironmentVariables.Telemetry.EnvironmentVariablesOpenedEvent());
window = new MainWindow();
window.Activate();
}
private Window window;
}
}

View File

@ -0,0 +1,27 @@
// 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 EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => false,
_ => true,
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

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 EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => string.Empty,
EnvironmentState.ChangedOnStartup => resourceLoader.GetString("StateNotUpToDateOnStartupMsg"),
EnvironmentState.EnvironmentMessageReceived => resourceLoader.GetString("StateNotUpToDateEnvironmentMessageReceivedMsg"),
EnvironmentState.ProfileNotApplicable => resourceLoader.GetString("StateProfileNotApplicableMsg"),
_ => throw new NotImplementedException(),
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,29 @@
// 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 EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToTitleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.ProfileNotApplicable => resourceLoader.GetString("ProfileNotApplicableTitle"),
_ => resourceLoader.GetString("StateNotUpToDateTitle"),
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,28 @@
// 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 EnvironmentVariables.Models;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class EnvironmentStateToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = (EnvironmentState)value;
return type switch
{
EnvironmentState.Unchanged => Visibility.Collapsed,
_ => Visibility.Visible,
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,31 @@
// 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 EnvironmentVariables.Models;
using Microsoft.UI.Xaml.Data;
namespace EnvironmentVariables.Converters;
public class VariableTypeToGlyphConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var type = (VariablesSetType)value;
return type switch
{
VariablesSetType.User => "\uE77B",
VariablesSetType.System => "\uE977",
VariablesSetType.Profile => "\uEDE3",
VariablesSetType.Path => "\uE8AC",
VariablesSetType.Duplicate => "\uE8C8",
_ => throw new NotImplementedException(),
};
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<winuiex:WindowEx
x:Class="EnvironmentVariables.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:EnvironmentVariables"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="using:EnvironmentVariables.Views"
xmlns:winuiex="using:WinUIEx"
x:Uid="Window"
MinWidth="700"
MinHeight="480"
mc:Ignorable="d">
<Window.SystemBackdrop>
<MicaBackdrop />
</Window.SystemBackdrop>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
x:Name="titleBar"
Height="32"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0" />
<ColumnDefinition x:Name="IconColumn" Width="Auto" />
<ColumnDefinition x:Name="TitleColumn" Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="1"
Width="16"
Height="16"
VerticalAlignment="Center"
Source="../Assets/EnvironmentVariables/EnvironmentVariables.ico" />
<TextBlock
x:Name="AppTitleTextBlock"
Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}" />
</Grid>
<views:MainPage Grid.Row="1" />
</Grid>
</winuiex:WindowEx>

View File

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Helpers.Win32;
using EnvironmentVariables.ViewModels;
using Microsoft.UI.Dispatching;
using WinUIEx;
namespace EnvironmentVariables
{
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : WindowEx
{
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true;
SetTitleBar(titleBar);
AppWindow.SetIcon("Assets/EnvironmentVariables/EnvironmentVariables.ico");
var loader = ResourceLoaderInstance.ResourceLoader;
var title = App.GetService<IElevationHelper>().IsElevated ? loader.GetString("WindowAdminTitle") : loader.GetString("WindowTitle");
Title = title;
AppTitleTextBlock.Text = title;
RegisterWindow();
}
private static readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
private static NativeMethods.WinProc newWndProc;
private static IntPtr oldWndProc = IntPtr.Zero;
private void RegisterWindow()
{
newWndProc = new NativeMethods.WinProc(WndProc);
var handle = this.GetWindowHandle();
oldWndProc = NativeMethods.SetWindowLongPtr(handle, NativeMethods.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
}
private static IntPtr WndProc(IntPtr hWnd, NativeMethods.WindowMessage msg, IntPtr wParam, IntPtr lParam)
{
switch (msg)
{
case NativeMethods.WindowMessage.WM_SETTINGSCHANGED:
{
var lParamStr = Marshal.PtrToStringUTF8(lParam);
if (lParamStr == "Environment")
{
// Do not react on self - not nice, re-check this
if (wParam != (IntPtr)0x12345)
{
var viewModel = App.GetService<MainViewModel>();
viewModel.EnvironmentState = Models.EnvironmentState.EnvironmentMessageReceived;
}
}
break;
}
default:
break;
}
return NativeMethods.CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
}
}

View File

@ -0,0 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
</Style>
</ResourceDictionary>

View File

@ -0,0 +1,744 @@
<Page
x:Class="EnvironmentVariables.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:EnvironmentVariables.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:EnvironmentVariables.Models"
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
x:Name="RootPage"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<!-- These resource dictionaries are needed to reference styles part of SettingsControls -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsCard/SettingsCard.xaml" />
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate x:Key="VariableTemplate" x:DataType="models:Variable">
<controls:SettingsCard
CommandParameter="{x:Bind (models:Variable)}"
Header="{x:Bind Name, Mode=TwoWay}"
IsActionIconVisible="False"
IsClickEnabled="False"
Style="{StaticResource DefaultSettingsExpanderItemStyle}">
<controls:SettingsCard.Description>
<StackPanel HorizontalAlignment="Left">
<ItemsControl
x:Name="VariableValuesList"
HorizontalAlignment="Left"
ItemsSource="{x:Bind ValuesList, Mode=TwoWay}"
Visibility="{x:Bind ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock
Margin="0,2,0,2"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
IsTextSelectionEnabled="True"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock
IsTextSelectionEnabled="True"
Text="{x:Bind Values, Mode=TwoWay}"
Visibility="{x:Bind ShowAsList, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
</StackPanel>
</controls:SettingsCard.Description>
<Button Content="{ui:FontIcon Glyph=&#xE712;}" Style="{StaticResource SubtleButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="EditItem"
Click="EditVariable_Click"
CommandParameter="{x:Bind (models:Variable)}"
Icon="{ui:FontIcon Glyph=&#xE70F;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="RemoveItem"
Click="Delete_Variable_Click"
CommandParameter="{x:Bind (models:Variable)}"
Icon="{ui:FontIcon Glyph=&#xE74D;}" />
</MenuFlyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<TextBlock x:Uid="More_Options_ButtonTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</Button>
</controls:SettingsCard>
</DataTemplate>
<converters:VariableTypeToGlyphConverter x:Key="VariableTypeToGlyphConverter" />
<converters:EnvironmentStateToBoolConverter x:Key="EnvironmentStateToBoolConverter" />
<converters:EnvironmentStateToMessageConverter x:Key="EnvironmentStateToMessageConverter" />
<converters:EnvironmentStateToTitleConverter x:Key="EnvironmentStateToTitleConverter" />
<converters:EnvironmentStateToVisibilityConverter x:Key="EnvironmentStateToVisibilityConverter" />
<toolkitConverters:BoolToVisibilityConverter
x:Key="BoolToInvertedVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
<toolkitConverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<toolkitConverters:BoolNegationConverter x:Key="BoolNegationConverter" />
</ResourceDictionary>
</Page.Resources>
<i:Interaction.Behaviors>
<ic:EventTriggerBehavior EventName="Loaded">
<ic:InvokeCommandAction Command="{x:Bind ViewModel.LoadEnvironmentVariablesCommand}" />
</ic:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Grid Margin="16" RowSpacing="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- buttons -->
<RowDefinition Height="Auto" />
<!-- Warning messages -->
<RowDefinition Height="*" />
<!-- content -->
<RowDefinition Height="Auto" />
<!-- content -->
</Grid.RowDefinitions>
<!-- buttons -->
<StackPanel Orientation="Horizontal">
<Button Command="{x:Bind NewProfileCommand}">
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
x:Name="Icon"
FontSize="16"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xe710;" />
<TextBlock x:Uid="NewProfileBtn" />
</StackPanel>
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="N" Modifiers="Control" />
</Button.KeyboardAccelerators>
</Button>
</StackPanel>
<Grid Grid.Row="1">
<InfoBar
x:Name="InvalidStateInfoBar"
x:Uid="InvalidStateInfoBar"
Title="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToTitleConverter}}"
CloseButtonClick="InvalidStateInfoBar_CloseButtonClick"
IsOpen="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToBoolConverter}}"
Message="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToMessageConverter}}"
Severity="Warning"
Visibility="{x:Bind ViewModel.EnvironmentState, Mode=OneWay, Converter={StaticResource EnvironmentStateToVisibilityConverter}}">
<InfoBar.ActionButton>
<Button
Click="ReloadButton_Click"
Content="{ui:FontIcon Glyph=&#xe72c;}"
Style="{StaticResource SubtleButtonStyle}"
Visibility="{x:Bind ViewModel.IsInfoBarButtonVisible, Mode=OneWay}" />
</InfoBar.ActionButton>
</InfoBar>
</Grid>
<Grid
Grid.Row="2"
Margin="0,24,0,0"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<!-- Left side -->
<ColumnDefinition Width="*" />
<!-- GridSplitter -->
<ColumnDefinition Width="Auto" />
<!-- Applied values -->
<ColumnDefinition Width="480" />
</Grid.ColumnDefinitions>
<ScrollViewer
Grid.Row="1"
Grid.Column="0"
VerticalScrollBarVisibility="Auto">
<StackPanel Spacing="4">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock x:Uid="ProfilesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
<FontIcon
FontSize="16"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="ProfilesDescriptionLbl" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<ItemsControl Margin="0,4,0,0" ItemsSource="{x:Bind ViewModel.Profiles, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:ProfileVariablesSet">
<controls:SettingsExpander
Margin="0,0,0,4"
Header="{x:Bind Name, Mode=TwoWay}"
HeaderIcon="{ui:FontIcon Glyph=&#xEDE3;}">
<controls:SettingsExpander.ItemsHeader>
<ItemsRepeater
ItemTemplate="{StaticResource VariableTemplate}"
ItemsSource="{x:Bind Variables, Mode=OneWay}"
TabFocusNavigation="Local"
VerticalCacheLength="10" />
</controls:SettingsExpander.ItemsHeader>
<StackPanel Orientation="Horizontal" Spacing="12">
<ToggleSwitch
x:Uid="ToggleSwitch"
IsOn="{x:Bind Mode=TwoWay, Path=IsEnabled}"
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}" />
<Button Content="{ui:FontIcon Glyph=&#xE712;}" Style="{StaticResource SubtleButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="EditItem"
Click="EditProfileBtn_Click"
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
Icon="{ui:FontIcon Glyph=&#xE70F;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="RemoveItem"
Click="RemoveProfileBtn_Click"
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
Icon="{ui:FontIcon Glyph=&#xE74D;}" />
</MenuFlyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<TextBlock x:Uid="More_Options_ButtonTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</controls:SettingsExpander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel
Margin="0,24,0,0"
Orientation="Horizontal"
Spacing="8">
<TextBlock x:Uid="DefaultVariablesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
<FontIcon
FontSize="16"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="DefaultVariablesDescriptionLbl" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<controls:SettingsExpander
Margin="0,4,0,0"
Header="{x:Bind ViewModel.UserDefaultSet.Name}"
HeaderIcon="{ui:FontIcon Glyph=&#xE77B;}">
<controls:SettingsExpander.ItemsHeader>
<ItemsRepeater
ItemTemplate="{StaticResource VariableTemplate}"
ItemsSource="{x:Bind ViewModel.UserDefaultSet.Variables, Mode=OneWay}"
TabFocusNavigation="Local"
VerticalCacheLength="10" />
</controls:SettingsExpander.ItemsHeader>
<StackPanel Orientation="Horizontal">
<Button
x:Name="AddDefaultVariableUserBtn"
x:Uid="AddVariableContent"
Click="AddDefaultVariableBtn_Click"
CommandParameter="{x:Bind ViewModel.UserDefaultSet}"
Content="Add variable"
Style="{StaticResource AccentButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="AddVariableTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</controls:SettingsExpander>
<controls:SettingsExpander Header="{x:Bind ViewModel.SystemDefaultSet.Name}" HeaderIcon="{ui:FontIcon Glyph=&#xE977;}">
<controls:SettingsExpander.ItemsHeader>
<ItemsRepeater
ItemTemplate="{StaticResource VariableTemplate}"
ItemsSource="{x:Bind ViewModel.SystemDefaultSet.Variables, Mode=OneWay}"
TabFocusNavigation="Local"
VerticalCacheLength="10" />
</controls:SettingsExpander.ItemsHeader>
<controls:SettingsExpander.Description>
<StackPanel
Orientation="Horizontal"
Spacing="4"
Visibility="{x:Bind ViewModel.IsElevated, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}">
<Border
Width="12"
Height="12"
AutomationProperties.AccessibilityView="Raw"
Background="{ThemeResource AccentFillColorDefaultBrush}"
CornerRadius="12">
<FontIcon
FontSize="8"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
Glyph="&#xEA1F;" />
</Border>
<TextBlock x:Uid="EditSystemDefaultSetInfoBar" />
</StackPanel>
</controls:SettingsExpander.Description>
<StackPanel Orientation="Horizontal">
<Button
x:Name="AddDefaultVariableSystemBtn"
x:Uid="AddVariableContent"
Click="AddDefaultVariableBtn_Click"
CommandParameter="{x:Bind ViewModel.SystemDefaultSet}"
IsEnabled="{x:Bind ViewModel.IsElevated}"
Style="{StaticResource AccentButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="AddVariableTooltip" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</controls:SettingsExpander>
</StackPanel>
</ScrollViewer>
<controls:GridSplitter
x:Name="Splitter"
Grid.Row="2"
Grid.Column="1"
Width="8"
Foreground="Transparent"
ResizeBehavior="BasedOnAlignment"
ResizeDirection="Auto" />
<Grid
x:Name="AppliedValuesPanel"
Grid.Row="2"
Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock x:Uid="AppliedVariablesLbl" Style="{StaticResource BodyStrongTextBlockStyle}" />
<FontIcon
FontSize="16"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xE946;">
<ToolTipService.ToolTip>
<TextBlock x:Uid="AppliedVariablesDescriptionLbl" TextWrapping="Wrap" />
</ToolTipService.ToolTip>
</FontIcon>
</StackPanel>
<Grid
Grid.Row="1"
Margin="0,8,0,0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="{ThemeResource ControlCornerRadius}">
<ScrollViewer x:Name="AppliedVariablesScrollViewer" HorizontalScrollBarVisibility="Auto">
<ItemsControl Margin="12,8,12,0" ItemsSource="{x:Bind ViewModel.AppliedVariables, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:Variable">
<Grid Height="56" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="{x:Bind ParentType, Mode=OneWay, Converter={StaticResource VariableTypeToGlyphConverter}}" />
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock
Grid.Row="1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Values}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap">
<ToolTipService.ToolTip>
<TextBlock Text="{x:Bind Values}" TextWrapping="WrapWholeWords" />
</ToolTipService.ToolTip>
</TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
<ContentDialog
x:Name="AddDefaultVariableDialog"
x:Uid="AddDefaultVariableDialog"
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
IsSecondaryButtonEnabled="True"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<ContentDialog.DataContext>
<models:Variable />
</ContentDialog.DataContext>
<ScrollViewer>
<StackPanel
MinWidth="320"
HorizontalAlignment="Stretch"
Spacing="16">
<TextBox
x:Uid="AddNewVariableName"
IsSpellCheckEnabled="False"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="AddDefaultVariableNameTxtBox_TextChanged" />
<TextBox
x:Uid="AddNewVariableValue"
AcceptsReturn="False"
IsSpellCheckEnabled="False"
ScrollViewer.IsVerticalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
Text="{Binding Values, Mode=TwoWay}"
TextWrapping="Wrap" />
</StackPanel>
</ScrollViewer>
</ContentDialog>
<ContentDialog
x:Name="EditVariableDialog"
x:Uid="EditVariableDialog"
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
IsSecondaryButtonEnabled="True"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<ContentDialog.DataContext>
<models:Variable />
</ContentDialog.DataContext>
<ScrollViewer>
<StackPanel
MinWidth="320"
HorizontalAlignment="Stretch"
Spacing="16">
<TextBox
x:Name="EditVariableDialogNameTxtBox"
x:Uid="AddNewVariableName"
IsSpellCheckEnabled="False"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextChanged="EditVariableDialogNameTxtBox_TextChanged" />
<TextBox
x:Name="EditVariableDialogValueTxtBox"
x:Uid="AddNewVariableValue"
AcceptsReturn="False"
IsSpellCheckEnabled="False"
ScrollViewer.IsVerticalRailEnabled="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
Text="{Binding Values, Mode=TwoWay}"
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
TextWrapping="Wrap" />
<ListView
x:Name="EditVariableValuesList"
Margin="-16,-8,0,12"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ValuesList, Mode=TwoWay}"
SelectionMode="None"
Visibility="{Binding ShowAsList, Converter={StaticResource BoolToVisibilityConverter}}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<TextBox
Background="Transparent"
BorderBrush="Transparent"
LostFocus="EditVariableValuesListTextBox_LostFocus"
Text="{Binding Text}" />
<Button
x:Uid="More_Options_Button"
Grid.Column="1"
VerticalAlignment="Center"
Content="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Style="{StaticResource SubtleButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="MoveUp"
Click="ReorderButtonUp_Click"
Icon="{ui:FontIcon Glyph=&#xE74A;}" />
<MenuFlyoutItem
x:Uid="MoveDown"
Click="ReorderButtonDown_Click"
Icon="{ui:FontIcon Glyph=&#xE74B;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="RemoveListItem"
Click="RemoveListVariableButton_Click"
Icon="{ui:FontIcon Glyph=&#xE74D;}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="InsertListEntryBefore"
Click="InsertListEntryBeforeButton_Click"
Icon="{ui:FontIcon Glyph=&#xECC8;}" />
<MenuFlyoutItem
x:Uid="InsertListEntryAfter"
Click="InsertListEntryAfterButton_Click"
Icon="{ui:FontIcon Glyph=&#xECC8;}" />
</MenuFlyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<TextBlock x:Uid="More_Options_ButtonTooltip" />
</ToolTipService.ToolTip>
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</ScrollViewer>
</ContentDialog>
<ContentDialog
x:Name="AddProfileDialog"
x:Uid="AddProfileDialog"
IsPrimaryButtonEnabled="{Binding Valid, Mode=OneWay}"
IsSecondaryButtonEnabled="True"
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
<ContentDialog.DataContext>
<models:ProfileVariablesSet />
</ContentDialog.DataContext>
<ScrollViewer>
<StackPanel
MinWidth="360"
HorizontalAlignment="Stretch"
Spacing="12">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
x:Uid="NewProfileNameTxtBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
IsSpellCheckEnabled="False"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ToggleSwitch
x:Uid="NewProfileEnabled"
Grid.Column="1"
MinWidth="0"
Margin="0,0,0,0"
VerticalAlignment="Center"
IsOn="{Binding IsEnabled, Mode=TwoWay}"
OffContent=""
OnContent="" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock x:Uid="NewProfileVariablesListViewHeader" Margin="0,12,0,8" />
<TextBlock
x:Uid="NewProfileVariablesListViewApplyToSystemHeader"
Grid.Column="1"
Margin="0,12,0,8"
HorizontalAlignment="Right"
Visibility="Collapsed" />
<ListView
x:Name="NewProfileVariablesListView"
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="-16,-8,0,12"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Variables, Mode=TwoWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Variable">
<Grid Height="48" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<TextBlock
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Values}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
</StackPanel>
<ToggleSwitch
x:Uid="ApplyAsSystemBtn"
Grid.Column="1"
IsOn="{x:Bind Path=ApplyToSystem, Mode=TwoWay}"
Visibility="Collapsed" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Button>
<StackPanel Orientation="Horizontal" Spacing="8">
<FontIcon
x:Name="AddVariableIcon"
FontSize="16"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="&#xe710;" />
<TextBlock x:Uid="AddVariableBtn" />
</StackPanel>
<Button.Flyout>
<Flyout
x:Name="AddVariableFlyout"
Closed="AddVariableFlyout_Closed"
Placement="Right">
<Grid Width="320" Height="480">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<controls:Segmented
x:Name="SwitchViewsSegmentedView"
MaxWidth="500"
HorizontalAlignment="Stretch"
SelectionMode="Single">
<controls:SegmentedItem
x:Name="AddNewVariableSegmentedItem"
x:Uid="NewVariableSegmentedButton"
Tag="NewVariable" />
<controls:SegmentedItem
x:Name="AddExistingVariableSegmentedItem"
x:Uid="ExistingVariableSegmentedButton"
Tag="ExistingVariable" />
</controls:Segmented>
<controls:SwitchPresenter
x:Name="AddVariableSwitchPresenter"
Grid.Row="1"
Value="{Binding SelectedItem.Tag, ElementName=SwitchViewsSegmentedView}">
<controls:Case Value="NewVariable">
<StackPanel Grid.Row="1" Orientation="Vertical">
<!-- Adding new variable -->
<TextBox
x:Name="AddNewVariableName"
x:Uid="AddNewVariableName"
Margin="0,16,0,0"
TextChanged="AddNewVariableName_TextChanged" />
<TextBox
x:Name="AddNewVariableValue"
x:Uid="AddNewVariableValue"
Margin="0,16,0,0" />
</StackPanel>
</controls:Case>
<controls:Case Value="ExistingVariable">
<!-- Adding existing variables -->
<ListView
x:Name="ExistingVariablesListView"
Grid.Row="1"
Margin="-12,12,0,12"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.DefaultVariables.Variables, Mode=OneWay}"
Loaded="ExistingVariablesListView_Loaded"
SelectionChanged="ExistingVariablesListView_SelectionChanged"
SelectionMode="Multiple">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Variable">
<Grid Height="48" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<FontIcon
Grid.Column="0"
VerticalAlignment="Center"
FontSize="14"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Glyph="{x:Bind ParentType, Mode=OneWay, Converter={StaticResource VariableTypeToGlyphConverter}}" />
<StackPanel
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
<TextBlock
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Values}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</controls:Case>
</controls:SwitchPresenter>
<Grid Grid.Row="2" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
x:Name="ConfirmAddVariableBtn"
x:Uid="ConfirmAddVariableBtn"
Margin="4,0,4,0"
HorizontalAlignment="Stretch"
Command="{x:Bind AddVariableCommand}"
IsEnabled="False"
Style="{StaticResource AccentButtonStyle}" />
<Button
x:Name="CancelAddVariableBtn"
x:Uid="CancelAddVariableBtn"
Grid.Column="1"
Margin="4,0,4,0"
HorizontalAlignment="Stretch"
Command="{x:Bind CancelAddVariableCommand}" />
</Grid>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</ScrollViewer>
</ContentDialog>
</Grid>
</Page>

View File

@ -0,0 +1,551 @@
// 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.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Input;
using EnvironmentVariables.Models;
using EnvironmentVariables.ViewModels;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation.Collections;
namespace EnvironmentVariables.Views
{
public sealed partial class MainPage : Page
{
private sealed class RelayCommandParameter
{
public RelayCommandParameter(Variable variable, VariablesSet set)
{
Variable = variable;
this.Set = set;
}
public Variable Variable { get; set; }
public VariablesSet Set { get; set; }
}
public MainViewModel ViewModel { get; private set; }
public ICommand EditCommand => new RelayCommand<RelayCommandParameter>(EditVariable);
public ICommand NewProfileCommand => new AsyncRelayCommand(AddProfileAsync);
public ICommand AddProfileCommand => new RelayCommand(AddProfile);
public ICommand UpdateProfileCommand => new RelayCommand(UpdateProfile);
public ICommand AddVariableCommand => new RelayCommand(AddVariable);
public ICommand CancelAddVariableCommand => new RelayCommand(CancelAddVariable);
public ICommand AddDefaultVariableCommand => new RelayCommand<DefaultVariablesSet>(AddDefaultVariable);
public MainPage()
{
this.InitializeComponent();
ViewModel = App.GetService<MainViewModel>();
DataContext = ViewModel;
}
private async Task ShowEditDialogAsync(Variable variable, VariablesSet parentSet)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
EditVariableDialog.Title = resourceLoader.GetString("EditVariableDialog_Title");
EditVariableDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
EditVariableDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
EditVariableDialog.PrimaryButtonCommand = EditCommand;
EditVariableDialog.PrimaryButtonCommandParameter = new RelayCommandParameter(variable, parentSet);
var clone = variable.Clone();
EditVariableDialog.DataContext = clone;
await EditVariableDialog.ShowAsync();
}
private async void EditVariable_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var btn = sender as MenuFlyoutItem;
var variablesSet = btn.DataContext as VariablesSet;
var variable = btn.CommandParameter as Variable;
if (variable != null)
{
await ShowEditDialogAsync(variable, variablesSet);
}
}
private void EditVariable(RelayCommandParameter param)
{
var variableSet = param.Set as ProfileVariablesSet;
var original = param.Variable;
var edited = EditVariableDialog.DataContext as Variable;
ViewModel.EditVariable(original, edited, variableSet);
}
private async Task AddProfileAsync()
{
SwitchViewsSegmentedView.SelectedIndex = 0;
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
AddProfileDialog.Title = resourceLoader.GetString("AddNewProfileDialog_Title");
AddProfileDialog.PrimaryButtonText = resourceLoader.GetString("AddBtn");
AddProfileDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
AddProfileDialog.PrimaryButtonCommand = AddProfileCommand;
AddProfileDialog.DataContext = new ProfileVariablesSet(Guid.NewGuid(), string.Empty);
await AddProfileDialog.ShowAsync();
}
private void AddProfile()
{
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
ViewModel.AddProfile(profile);
}
private void UpdateProfile()
{
var updatedProfile = AddProfileDialog.DataContext as ProfileVariablesSet;
ViewModel.UpdateProfile(updatedProfile);
}
private async void RemoveProfileBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var button = sender as MenuFlyoutItem;
var profile = button.CommandParameter as ProfileVariablesSet;
if (profile != null)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
ContentDialog dialog = new ContentDialog();
dialog.XamlRoot = RootPage.XamlRoot;
dialog.Title = profile.Name;
dialog.PrimaryButtonText = resourceLoader.GetString("Yes");
dialog.CloseButtonText = resourceLoader.GetString("No");
dialog.DefaultButton = ContentDialogButton.Primary;
dialog.Content = new TextBlock() { Text = resourceLoader.GetString("Delete_Dialog_Description"), TextWrapping = Microsoft.UI.Xaml.TextWrapping.WrapWholeWords };
dialog.PrimaryButtonClick += (s, args) =>
{
ViewModel.RemoveProfile(profile);
};
var result = await dialog.ShowAsync();
}
}
private void AddVariable()
{
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
if (profile != null)
{
if (AddVariableSwitchPresenter.Value as string == "NewVariable")
{
profile.Variables.Add(new Variable(AddNewVariableName.Text, AddNewVariableValue.Text, VariablesSetType.Profile));
}
else
{
foreach (Variable variable in ExistingVariablesListView.SelectedItems)
{
if (!profile.Variables.Where(x => x.Name == variable.Name).Any())
{
var clone = variable.Clone(true);
profile.Variables.Add(clone);
}
}
}
}
AddNewVariableName.Text = string.Empty;
AddNewVariableValue.Text = string.Empty;
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
ExistingVariablesListView.SelectedItems.Clear();
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
AddVariableFlyout.Hide();
}
private void CancelAddVariable()
{
AddNewVariableName.Text = string.Empty;
AddNewVariableValue.Text = string.Empty;
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
ExistingVariablesListView.SelectedItems.Clear();
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
AddVariableFlyout.Hide();
}
private void AddDefaultVariable(DefaultVariablesSet set)
{
var variable = AddDefaultVariableDialog.DataContext as Variable;
var type = set.Type;
ViewModel.AddDefaultVariable(variable, type);
}
private async void Delete_Variable_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
MenuFlyoutItem selectedItem = sender as MenuFlyoutItem;
var variableSet = selectedItem.DataContext as ProfileVariablesSet;
var variable = selectedItem.CommandParameter as Variable;
if (variable != null)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
ContentDialog dialog = new ContentDialog();
dialog.XamlRoot = RootPage.XamlRoot;
dialog.Title = variable.Name;
dialog.PrimaryButtonText = resourceLoader.GetString("Yes");
dialog.CloseButtonText = resourceLoader.GetString("No");
dialog.DefaultButton = ContentDialogButton.Primary;
dialog.Content = new TextBlock() { Text = resourceLoader.GetString("Delete_Variable_Description"), TextWrapping = Microsoft.UI.Xaml.TextWrapping.WrapWholeWords };
dialog.PrimaryButtonClick += (s, args) =>
{
ViewModel.DeleteVariable(variable, variableSet);
};
var result = await dialog.ShowAsync();
}
}
private void AddNewVariableName_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox nameTxtBox = sender as TextBox;
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
if (nameTxtBox != null)
{
if (nameTxtBox.Text.Length == 0 || nameTxtBox.Text.Length >= 255 || profile.Variables.Where(x => x.Name.Equals(nameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any())
{
ConfirmAddVariableBtn.IsEnabled = false;
}
else
{
ConfirmAddVariableBtn.IsEnabled = true;
}
}
}
private void ReloadButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
ViewModel.LoadEnvironmentVariables();
ViewModel.EnvironmentState = EnvironmentState.Unchanged;
}
private void ExistingVariablesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
int toRemove = -1;
if (e.AddedItems.Count > 0)
{
var list = sender as ListView;
var duplicates = list.SelectedItems.GroupBy(x => ((Variable)x).Name.ToLowerInvariant()).Where(g => g.Count() > 1).ToList();
foreach (var dup in duplicates)
{
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
list.SelectedItems.Remove(dup.ElementAt(1));
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
}
}
if (e.RemovedItems.Count > 0)
{
Variable removedVariable = e.RemovedItems[0] as Variable;
for (int i = 0; i < profile.Variables.Count; i++)
{
if (profile.Variables[i].Name == removedVariable.Name && profile.Variables[i].Values == removedVariable.Values)
{
toRemove = i;
break;
}
}
if (toRemove != -1)
{
profile.Variables.RemoveAt(toRemove);
}
}
ConfirmAddVariableBtn.IsEnabled = false;
foreach (Variable variable in ExistingVariablesListView.SelectedItems)
{
if (variable != null)
{
if (!profile.Variables.Where(x => x.Name.Equals(variable.Name, StringComparison.Ordinal) && x.Values.Equals(variable.Values, StringComparison.Ordinal)).Any())
{
ConfirmAddVariableBtn.IsEnabled = true;
break;
}
}
}
}
private async void EditProfileBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
SwitchViewsSegmentedView.SelectedIndex = 0;
var button = sender as MenuFlyoutItem;
var profile = button.CommandParameter as ProfileVariablesSet;
if (profile != null)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
AddProfileDialog.Title = resourceLoader.GetString("EditProfileDialog_Title");
AddProfileDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
AddProfileDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
AddProfileDialog.PrimaryButtonCommand = UpdateProfileCommand;
AddProfileDialog.DataContext = profile.Clone();
await AddProfileDialog.ShowAsync();
}
}
private void ExistingVariablesListView_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var profile = AddProfileDialog.DataContext as ProfileVariablesSet;
foreach (Variable item in ExistingVariablesListView.Items)
{
if (item != null)
{
foreach (var profileItem in profile.Variables)
{
if (profileItem.Name == item.Name && profileItem.Values == item.Values)
{
if (ExistingVariablesListView.SelectedItems.Where(x => ((Variable)x).Name.Equals(profileItem.Name, StringComparison.OrdinalIgnoreCase)).Any())
{
continue;
}
ExistingVariablesListView.SelectionChanged -= ExistingVariablesListView_SelectionChanged;
ExistingVariablesListView.SelectedItems.Add(item);
ExistingVariablesListView.SelectionChanged += ExistingVariablesListView_SelectionChanged;
}
}
}
}
}
private async Task ShowAddDefaultVariableDialogAsync(DefaultVariablesSet set)
{
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
AddDefaultVariableDialog.Title = resourceLoader.GetString("AddVariable_Title");
AddDefaultVariableDialog.PrimaryButtonText = resourceLoader.GetString("SaveBtn");
AddDefaultVariableDialog.SecondaryButtonText = resourceLoader.GetString("CancelBtn");
AddDefaultVariableDialog.PrimaryButtonCommand = AddDefaultVariableCommand;
AddDefaultVariableDialog.PrimaryButtonCommandParameter = set;
var variableType = set.Id == VariablesSet.SystemGuid ? VariablesSetType.System : VariablesSetType.User;
AddDefaultVariableDialog.DataContext = new Variable(string.Empty, string.Empty, variableType);
await AddDefaultVariableDialog.ShowAsync();
}
private async void AddDefaultVariableBtn_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var button = sender as Button;
var defaultVariableSet = button.CommandParameter as DefaultVariablesSet;
if (defaultVariableSet != null)
{
await ShowAddDefaultVariableDialogAsync(defaultVariableSet);
}
}
private void EditVariableDialogNameTxtBox_TextChanged(object sender, TextChangedEventArgs e)
{
var variable = EditVariableDialog.DataContext as Variable;
var param = EditVariableDialog.PrimaryButtonCommandParameter as RelayCommandParameter;
var variableSet = param.Set;
if (variableSet == null)
{
// default set
variableSet = variable.ParentType == VariablesSetType.User ? ViewModel.UserDefaultSet : ViewModel.SystemDefaultSet;
}
if (variableSet != null)
{
if (variableSet.Variables.Where(x => x.Name.Equals(EditVariableDialogNameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any() || !variable.Valid)
{
EditVariableDialog.IsPrimaryButtonEnabled = false;
}
else
{
EditVariableDialog.IsPrimaryButtonEnabled = true;
}
}
if (!variable.Validate())
{
EditVariableDialog.IsPrimaryButtonEnabled = false;
}
}
private void AddDefaultVariableNameTxtBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox nameTxtBox = sender as TextBox;
var variable = AddDefaultVariableDialog.DataContext as Variable;
var defaultSet = variable.ParentType == VariablesSetType.User ? ViewModel.UserDefaultSet : ViewModel.SystemDefaultSet;
if (nameTxtBox != null)
{
if (nameTxtBox.Text.Length == 0 || defaultSet.Variables.Where(x => x.Name.Equals(nameTxtBox.Text, StringComparison.OrdinalIgnoreCase)).Any())
{
AddDefaultVariableDialog.IsPrimaryButtonEnabled = false;
}
else
{
AddDefaultVariableDialog.IsPrimaryButtonEnabled = true;
}
}
if (!variable.Validate())
{
AddDefaultVariableDialog.IsPrimaryButtonEnabled = false;
}
}
private void EditVariableDialogValueTxtBox_TextChanged(object sender, TextChangedEventArgs e)
{
var txtBox = sender as TextBox;
var variable = EditVariableDialog.DataContext as Variable;
EditVariableDialog.IsPrimaryButtonEnabled = true;
variable.ValuesList = Variable.ValuesStringToValuesListItemCollection(txtBox.Text);
}
private void ReorderButtonUp_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
var variable = EditVariableDialog.DataContext as Variable;
var index = variable.ValuesList.IndexOf(listItem);
if (index > 0)
{
variable.ValuesList.Move(index, index - 1);
}
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
private void ReorderButtonDown_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
var variable = EditVariableDialog.DataContext as Variable;
var btn = EditVariableDialog.PrimaryButtonCommandParameter as Button;
var index = variable.ValuesList.IndexOf(listItem);
if (index < variable.ValuesList.Count - 1)
{
variable.ValuesList.Move(index, index + 1);
}
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
private void RemoveListVariableButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = ((MenuFlyoutItem)sender).DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
var variable = EditVariableDialog.DataContext as Variable;
variable.ValuesList.Remove(listItem);
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
private void InsertListEntryBeforeButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = (sender as MenuFlyoutItem)?.DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
var variable = EditVariableDialog.DataContext as Variable;
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
}
private void InsertListEntryAfterButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = (sender as MenuFlyoutItem)?.DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
var variable = EditVariableDialog.DataContext as Variable;
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
}
private void EditVariableValuesListTextBox_LostFocus(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
var listItem = (sender as TextBox)?.DataContext as Variable.ValuesListItem;
if (listItem == null)
{
return;
}
if (listItem.Text == (sender as TextBox)?.Text)
{
return;
}
listItem.Text = (sender as TextBox)?.Text;
var variable = EditVariableDialog.DataContext as Variable;
var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
}
private void InvalidStateInfoBar_CloseButtonClick(InfoBar sender, object args)
{
ViewModel.EnvironmentState = EnvironmentState.Unchanged;
}
private void AddVariableFlyout_Closed(object sender, object e)
{
CancelAddVariable();
ConfirmAddVariableBtn.IsEnabled = false;
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Security.Principal;
namespace EnvironmentVariables.Helpers
{
public class ElevationHelper : IElevationHelper
{
private readonly bool _isElevated;
public bool IsElevated => _isElevated;
public ElevationHelper()
{
_isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
}
}
}

View File

@ -0,0 +1,189 @@
// 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;
using System.Collections.Generic;
using EnvironmentVariables.Helpers.Win32;
using EnvironmentVariables.Models;
using ManagedCommon;
using Microsoft.Win32;
namespace EnvironmentVariables.Helpers
{
internal sealed class EnvironmentVariablesHelper
{
internal static string GetBackupVariableName(Variable variable, string profileName)
{
return variable.Name + "_PowerToys_" + profileName;
}
internal static Variable GetExisting(string variableName)
{
var userVariables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
foreach (DictionaryEntry variable in userVariables)
{
var key = variable.Key as string;
if (key.Equals(variableName, StringComparison.OrdinalIgnoreCase))
{
return new Variable(key, userVariables[key] as string, VariablesSetType.User);
}
}
var systemVariables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine);
foreach (DictionaryEntry variable in systemVariables)
{
var key = variable.Key as string;
if (key.Equals(variableName, StringComparison.OrdinalIgnoreCase))
{
return new Variable(key, systemVariables[key] as string, VariablesSetType.System);
}
}
return null;
}
private static RegistryKey OpenEnvironmentKeyIfExists(bool fromMachine, bool writable)
{
RegistryKey baseKey;
string keyName;
if (fromMachine)
{
baseKey = Registry.LocalMachine;
keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
}
else
{
baseKey = Registry.CurrentUser;
keyName = "Environment";
}
return baseKey.OpenSubKey(keyName, writable: writable);
}
private static void SetEnvironmentVariableFromRegistryWithoutNotify(string variable, string value, bool fromMachine)
{
const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars
if (!fromMachine && variable.Length >= MaxUserEnvVariableLength)
{
Logger.LogError("Can't apply variable - name too long.");
return;
}
using (RegistryKey environmentKey = OpenEnvironmentKeyIfExists(fromMachine, writable: true))
{
if (environmentKey != null)
{
if (value == null)
{
environmentKey.DeleteValue(variable, throwOnMissingValue: false);
}
else
{
environmentKey.SetValue(variable, value);
}
}
}
}
internal static void NotifyEnvironmentChange()
{
unsafe
{
// send a WM_SETTINGCHANGE message to all windows
fixed (char* lParam = "Environment")
{
_ = NativeMethods.SendNotifyMessage(new IntPtr(NativeMethods.HWND_BROADCAST), NativeMethods.WindowMessage.WM_SETTINGSCHANGED, (IntPtr)0x12345, (IntPtr)lParam);
}
}
}
internal static void GetVariables(EnvironmentVariableTarget target, VariablesSet set)
{
var variables = Environment.GetEnvironmentVariables(target);
var sortedList = new SortedList<string, Variable>();
foreach (DictionaryEntry variable in variables)
{
string key = variable.Key as string;
string value = variable.Value as string;
if (string.IsNullOrEmpty(key))
{
continue;
}
Variable entry = new Variable(key, value, set.Type);
sortedList.Add(key, entry);
}
set.Variables = new System.Collections.ObjectModel.ObservableCollection<Variable>(sortedList.Values);
}
internal static bool SetVariableWithoutNotify(Variable variable)
{
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
return true;
}
internal static bool SetVariable(Variable variable)
{
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, variable.Values, fromMachine);
NotifyEnvironmentChange();
return true;
}
internal static bool UnsetVariableWithoutNotify(Variable variable)
{
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
return true;
}
internal static bool UnsetVariable(Variable variable)
{
bool fromMachine = variable.ParentType switch
{
VariablesSetType.Profile => false,
VariablesSetType.User => false,
VariablesSetType.System => true,
_ => throw new NotImplementedException(),
};
SetEnvironmentVariableFromRegistryWithoutNotify(variable.Name, null, fromMachine);
NotifyEnvironmentChange();
return true;
}
}
}

View File

@ -0,0 +1,60 @@
// 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.IO;
using System.IO.Abstractions;
using System.Text.Json;
using System.Threading.Tasks;
using EnvironmentVariables.Models;
namespace EnvironmentVariables.Helpers
{
internal sealed class EnvironmentVariablesService : IEnvironmentVariablesService
{
private const string ProfilesJsonFileSubPath = "Microsoft\\PowerToys\\EnvironmentVariables\\";
private readonly string _profilesJsonFilePath;
private readonly IFileSystem _fileSystem;
private readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
WriteIndented = true,
};
public string ProfilesJsonFilePath => _profilesJsonFilePath;
public EnvironmentVariablesService(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
_profilesJsonFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ProfilesJsonFileSubPath, "profiles.json");
}
public void Dispose()
{
}
public List<ProfileVariablesSet> ReadProfiles()
{
if (!_fileSystem.File.Exists(ProfilesJsonFilePath))
{
return new List<ProfileVariablesSet>();
}
var fileContent = _fileSystem.File.ReadAllText(ProfilesJsonFilePath);
var profiles = JsonSerializer.Deserialize<List<ProfileVariablesSet>>(fileContent);
return profiles;
}
public async Task WriteAsync(IEnumerable<ProfileVariablesSet> profiles)
{
string jsonData = JsonSerializer.Serialize(profiles, _serializerOptions);
await _fileSystem.File.WriteAllTextAsync(ProfilesJsonFilePath, jsonData);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace EnvironmentVariables.Helpers
{
public interface IElevationHelper
{
bool IsElevated { get; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EnvironmentVariables.Models;
namespace EnvironmentVariables.Helpers
{
public interface IEnvironmentVariablesService : IDisposable
{
string ProfilesJsonFilePath { get; }
List<ProfileVariablesSet> ReadProfiles();
Task WriteAsync(IEnumerable<ProfileVariablesSet> profiles);
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace EnvironmentVariables.Helpers.Win32
{
public static class NativeMethods
{
internal const int HWND_BROADCAST = 0xffff;
internal delegate IntPtr WinProc(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int SendNotifyMessage(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll")]
internal static extern int GetDpiForWindow(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
internal static extern int SetWindowLong32(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);
[DllImport("user32.dll")]
internal static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam);
[Flags]
internal enum WindowLongIndexFlags : int
{
GWL_WNDPROC = -4,
}
internal enum WindowMessage : int
{
WM_SETTINGSCHANGED = 0x001A,
}
internal static IntPtr SetWindowLongPtr(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc)
{
if (IntPtr.Size == 8)
{
return SetWindowLongPtr64(hWnd, nIndex, newProc);
}
else
{
return new IntPtr(SetWindowLong32(hWnd, nIndex, newProc));
}
}
}
}

View File

@ -0,0 +1,17 @@
// 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.Windows.ApplicationModel.Resources;
namespace EnvironmentVariables.Helpers
{
internal static class ResourceLoaderInstance
{
internal static ResourceLoader ResourceLoader { get; private set; }
static ResourceLoaderInstance()
{
ResourceLoader = new ResourceLoader("PowerToys.EnvironmentVariables.pri");
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace EnvironmentVariables.Models
{
public class DefaultVariablesSet : VariablesSet
{
public DefaultVariablesSet(Guid id, string name, VariablesSetType type)
: base(id, name, type)
{
}
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace EnvironmentVariables.Models
{
public enum EnvironmentState
{
Unchanged = 0,
ChangedOnStartup,
EnvironmentMessageReceived,
ProfileNotApplicable,
}
}

View File

@ -0,0 +1,164 @@
// 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.ObjectModel;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using EnvironmentVariables.Helpers;
using ManagedCommon;
namespace EnvironmentVariables.Models
{
public partial class ProfileVariablesSet : VariablesSet
{
[ObservableProperty]
private bool _isEnabled;
public ProfileVariablesSet()
: base()
{
Type = VariablesSetType.Profile;
IconPath = ProfileIconPath;
}
public ProfileVariablesSet(Guid id, string name)
: base(id, name, VariablesSetType.Profile)
{
IconPath = ProfileIconPath;
}
public Task Apply()
{
return Task.Run(() =>
{
foreach (var variable in Variables)
{
var applyToSystem = variable.ApplyToSystem;
// Get existing variable with the same name if it exist
var variableToOverride = EnvironmentVariablesHelper.GetExisting(variable.Name);
// It exists. Rename it to preserve it.
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User)
{
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, this.Name);
// Backup the variable
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride))
{
Logger.LogError("Failed to set backup variable.");
}
}
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variable))
{
Logger.LogError("Failed to set profile variable.");
}
}
EnvironmentVariablesHelper.NotifyEnvironmentChange();
});
}
public Task UnApply()
{
return Task.Run(() =>
{
foreach (var variable in Variables)
{
UnapplyVariable(variable);
}
EnvironmentVariablesHelper.NotifyEnvironmentChange();
});
}
public void UnapplyVariable(Variable variable)
{
// Unset the variable
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(variable))
{
Logger.LogError("Failed to unset variable.");
}
var originalName = variable.Name;
var backupName = EnvironmentVariablesHelper.GetBackupVariableName(variable, this.Name);
// Get backup variable if it exist
var backupVariable = EnvironmentVariablesHelper.GetExisting(backupName);
if (backupVariable != null)
{
var variableToRestore = new Variable(originalName, backupVariable.Values, backupVariable.ParentType);
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable))
{
Logger.LogError("Failed to unset backup variable.");
}
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore))
{
Logger.LogError("Failed to restore backup variable.");
}
}
}
public bool IsCorrectlyApplied()
{
if (!IsEnabled)
{
return false;
}
foreach (var variable in Variables)
{
var applied = EnvironmentVariablesHelper.GetExisting(variable.Name);
if (applied != null && applied.Values == variable.Values && applied.ParentType == VariablesSetType.User)
{
continue;
}
return false;
}
return true;
}
public bool IsApplicable()
{
foreach (var variable in Variables)
{
if (!variable.Validate())
{
return false;
}
// Get existing variable with the same name if it exist
var variableToOverride = EnvironmentVariablesHelper.GetExisting(variable.Name);
// It exists. Backup is needed.
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User)
{
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, this.Name);
if (!variableToOverride.Validate())
{
return false;
}
}
}
return true;
}
public ProfileVariablesSet Clone()
{
var clone = new ProfileVariablesSet(this.Id, this.Name);
clone.Variables = new ObservableCollection<Variable>(this.Variables);
clone.IsEnabled = this.IsEnabled;
return clone;
}
}
}

View File

@ -0,0 +1,202 @@
// 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.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using EnvironmentVariables.Helpers;
using ManagedCommon;
namespace EnvironmentVariables.Models
{
public partial class Variable : ObservableObject, IJsonOnDeserialized
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Valid))]
[NotifyPropertyChangedFor(nameof(ShowAsList))]
private string _name;
[ObservableProperty]
private string _values;
[ObservableProperty]
private bool _applyToSystem;
[JsonIgnore]
public bool IsEditable
{
get
{
return ParentType != VariablesSetType.System || App.GetService<IElevationHelper>().IsElevated;
}
}
[JsonIgnore]
public VariablesSetType ParentType { get; set; }
// To store the strings in the Values List with actual objects that can be referenced and identity compared
public class ValuesListItem
{
public string Text { get; set; }
}
[ObservableProperty]
[property: JsonIgnore]
[JsonIgnore]
private ObservableCollection<ValuesListItem> _valuesList;
[JsonIgnore]
public bool Valid => Validate();
[JsonIgnore]
public bool ShowAsList => IsList();
private bool IsList()
{
List<string> listVariables = new() { "PATH", "PATHEXT", "PSMODULEPATH" };
foreach (var name in listVariables)
{
if (Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
public void OnDeserialized()
{
// No need to save ValuesList to the Json, so we are generating it after deserializing
ValuesList = ValuesStringToValuesListItemCollection(Values);
}
public Variable()
{
}
public Variable(string name, string values, VariablesSetType parentType)
{
Name = name;
Values = values;
ParentType = parentType;
ValuesList = ValuesStringToValuesListItemCollection(Values);
}
internal static ObservableCollection<ValuesListItem> ValuesStringToValuesListItemCollection(string values)
{
return new ObservableCollection<ValuesListItem>(values.Split(';').Select(x => new ValuesListItem { Text = x }));
}
internal Task Update(Variable edited, bool propagateChange, ProfileVariablesSet parentProfile)
{
bool nameChanged = Name != edited.Name;
var clone = this.Clone();
// Update state
Name = edited.Name;
Values = edited.Values;
ValuesList = ValuesStringToValuesListItemCollection(Values);
return Task.Run(() =>
{
// Apply changes
if (propagateChange)
{
if (nameChanged)
{
if (!EnvironmentVariablesHelper.UnsetVariable(clone))
{
Logger.LogError("Failed to unset original variable.");
}
if (parentProfile != null)
{
var backupName = EnvironmentVariablesHelper.GetBackupVariableName(clone, parentProfile.Name);
// Get backup variable if it exist
var backupVariable = EnvironmentVariablesHelper.GetExisting(backupName);
if (backupVariable != null)
{
var variableToRestore = new Variable(clone.Name, backupVariable.Values, backupVariable.ParentType);
if (!EnvironmentVariablesHelper.UnsetVariableWithoutNotify(backupVariable))
{
Logger.LogError("Failed to unset backup variable.");
}
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToRestore))
{
Logger.LogError("Failed to restore backup variable.");
}
}
}
}
// Get existing variable with the same name if it exist
var variableToOverride = EnvironmentVariablesHelper.GetExisting(Name);
// It exists. Rename it to preserve it.
if (variableToOverride != null && variableToOverride.ParentType == VariablesSetType.User && parentProfile != null)
{
// Gets which name the backup variable should have.
variableToOverride.Name = EnvironmentVariablesHelper.GetBackupVariableName(variableToOverride, parentProfile.Name);
// Only create a backup variable if there's not one already, to avoid overriding. (solves Path nuking errors, for example, after editing path on an enabled profile)
if (EnvironmentVariablesHelper.GetExisting(variableToOverride.Name) == null)
{
// Backup the variable
if (!EnvironmentVariablesHelper.SetVariableWithoutNotify(variableToOverride))
{
Logger.LogError("Failed to set backup variable.");
}
}
}
if (!EnvironmentVariablesHelper.SetVariable(this))
{
Logger.LogError("Failed to set new variable.");
}
}
});
}
internal Variable Clone(bool profile = false)
{
return new Variable
{
Name = Name,
Values = Values,
ParentType = profile ? VariablesSetType.Profile : ParentType,
ValuesList = ValuesStringToValuesListItemCollection(Values),
};
}
public bool Validate()
{
if (string.IsNullOrWhiteSpace(Name))
{
return false;
}
const int MaxUserEnvVariableLength = 255; // User-wide env vars stored in the registry have names limited to 255 chars
if (ParentType != VariablesSetType.System && Name.Length >= MaxUserEnvVariableLength)
{
Logger.LogError("Variable name too long.");
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,70 @@
// 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.ObjectModel;
using System.Linq;
using System.Text.Json.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using EnvironmentVariables.ViewModels;
namespace EnvironmentVariables.Models
{
public partial class VariablesSet : ObservableObject
{
public static readonly Guid UserGuid = new Guid("92F7AA9A-AE31-49CD-83C8-80A71E432AA5");
public static readonly Guid SystemGuid = new Guid("F679C74D-DB00-4795-92E1-B1F6A4833279");
private static readonly string UserIconPath = "/Assets/EnvironmentVariables/UserIcon.png";
private static readonly string SystemIconPath = "/Assets/EnvironmentVariables/SystemIcon.png";
protected static readonly string ProfileIconPath = "/Assets/EnvironmentVariables/ProfileIcon.png";
public Guid Id { get; set; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Valid))]
private string _name;
[JsonIgnore]
public VariablesSetType Type { get; set; }
[JsonIgnore]
public string IconPath { get; set; }
[ObservableProperty]
private ObservableCollection<Variable> _variables;
public bool Valid => Validate();
public VariablesSet()
{
}
public VariablesSet(Guid id, string name, VariablesSetType type)
{
Id = id;
Name = name;
Type = type;
Variables = new ObservableCollection<Variable>();
IconPath = Type switch
{
VariablesSetType.User => UserIconPath,
VariablesSetType.System => SystemIconPath,
VariablesSetType.Profile => ProfileIconPath,
_ => throw new NotImplementedException(),
};
}
private bool Validate()
{
if (string.IsNullOrWhiteSpace(Name))
{
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace EnvironmentVariables.Models
{
public enum VariablesSetType
{
Path = 0,
Duplicate,
Profile,
User,
System,
}
}

View File

@ -0,0 +1,45 @@
// 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.Threading;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
namespace EnvironmentVariables
{
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
Logger.InitializeLogger("\\EnvironmentVariables\\Logs");
WinRT.ComWrappersSupport.InitializeComWrappers();
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
return;
}
var instanceKey = AppInstance.FindOrRegisterForKey("PowerToys_EnvironmentVariables_Instance");
if (instanceKey.IsCurrent)
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
}
else
{
Logger.LogWarning("Another instance of Environment Variables is running. Exiting.");
}
}
}
}

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DefaultSetsLabel.Text" xml:space="preserve">
<value>Default variables</value>
</data>
<data name="DefaultVariablesLbl.Text" xml:space="preserve">
<value>Default variables</value>
</data>
<data name="NewProfileBtn.Text" xml:space="preserve">
<value>New profile</value>
</data>
<data name="ProfilesDescriptionLbl.Text" xml:space="preserve">
<value>You can create profiles to quickly apply a set of preconfigured variables</value>
</data>
<data name="ProfilesLbl.Text" xml:space="preserve">
<value>Profiles</value>
</data>
<data name="System" xml:space="preserve">
<value>System</value>
</data>
<data name="User" xml:space="preserve">
<value>User</value>
</data>
<data name="WindowTitle" xml:space="preserve">
<value>Environment Variables</value>
<comment>Title of the window when running as user</comment>
</data>
<data name="EditDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="EditVariableDialog_Title" xml:space="preserve">
<value>Edit variable</value>
</data>
<data name="NewProfileNameTxtBox.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="SaveBtn" xml:space="preserve">
<value>Save</value>
</data>
<data name="ValueTxtBox.Header" xml:space="preserve">
<value>Value</value>
</data>
<data name="AddBtn" xml:space="preserve">
<value>Save</value>
</data>
<data name="AddNewProfileDialog_Title" xml:space="preserve">
<value>New profile</value>
</data>
<data name="NewProfileEnabled.Header" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="AddVariableBtn.Text" xml:space="preserve">
<value>Add variable</value>
</data>
<data name="AddNewVariableName.Header" xml:space="preserve">
<value>Variable name</value>
</data>
<data name="AddNewVariableValue.Header" xml:space="preserve">
<value>Variable value</value>
</data>
<data name="ExistingVariableSegmentedButton.Content" xml:space="preserve">
<value>Existing</value>
</data>
<data name="NewVariableSegmentedButton.Content" xml:space="preserve">
<value>New</value>
</data>
<data name="WindowAdminTitle" xml:space="preserve">
<value>Administrator: Environment Variables</value>
<comment>Title of the window when running as administrator</comment>
</data>
<data name="CancelAddVariableBtn.Content" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ConfirmAddVariableBtn.Content" xml:space="preserve">
<value>Add</value>
</data>
<data name="AppliedVariablesDescriptionLbl.Text" xml:space="preserve">
<value>List of applied variables</value>
</data>
<data name="AppliedVariablesLbl.Text" xml:space="preserve">
<value>Applied variables</value>
</data>
<data name="NewProfileVariablesListViewHeader.Text" xml:space="preserve">
<value>Variables</value>
</data>
<data name="DeleteMenuItem.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="Delete_Dialog_Description" xml:space="preserve">
<value>Are you sure you want to delete this profile? Deleting applied profile will remove all profile variables.</value>
</data>
<data name="EditSystemDefaultSetInfoBar.Text" xml:space="preserve">
<value>Administrator permissions are required to edit System variables</value>
</data>
<data name="No" xml:space="preserve">
<value>No</value>
</data>
<data name="Yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="StateNotUpToDateTitle" xml:space="preserve">
<value>Changes were made outside of this app.</value>
</data>
<data name="StateNotUpToDateOnStartupMsg" xml:space="preserve">
<value>Variables included in applied profile have been modified. Review the latest changes before applying the profile again.</value>
</data>
<data name="CancelBtn" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="StateNotUpToDateEnvironmentMessageReceivedMsg" xml:space="preserve">
<value>Variables have been modified. Reload to get the latest changes.</value>
</data>
<data name="AddVariable_Title" xml:space="preserve">
<value>Add variable</value>
</data>
<data name="Delete_Variable_Description" xml:space="preserve">
<value>Are you sure you want to delete this variable?</value>
</data>
<data name="EditProfileDialog_Title" xml:space="preserve">
<value>Edit profile</value>
</data>
<data name="AddVariableTooltip.Text" xml:space="preserve">
<value>Add variable</value>
</data>
<data name="DefaultVariablesDescriptionLbl.Text" xml:space="preserve">
<value>Add, remove or edit USER and SYSTEM variables</value>
</data>
<data name="EditItem.Text" xml:space="preserve">
<value>Edit</value>
</data>
<data name="More_Options_ButtonTooltip.Text" xml:space="preserve">
<value>More options</value>
</data>
<data name="MoveDown.Text" xml:space="preserve">
<value>Move down</value>
</data>
<data name="MoveUp.Text" xml:space="preserve">
<value>Move up</value>
</data>
<data name="InsertListEntryBefore.Text" xml:space="preserve">
<value>Insert Before</value>
</data>
<data name="InsertListEntryAfter.Text" xml:space="preserve">
<value>Insert After</value>
</data>
<data name="NewProfileVariablesListViewApplyToSystemHeader.Text" xml:space="preserve">
<value>Apply to SYSTEM?</value>
</data>
<data name="RemoveListItem.Text" xml:space="preserve">
<value>Delete</value>
</data>
<data name="RemoveItem.Text" xml:space="preserve">
<value>Remove</value>
</data>
<data name="AddVariableContent.Content" xml:space="preserve">
<value>Add variable</value>
</data>
<data name="ProfileNotApplicableTitle" xml:space="preserve">
<value>Profile can not be applied.</value>
</data>
<data name="StateProfileNotApplicableMsg" xml:space="preserve">
<value>Variables or backup variables are invalid.</value>
</data>
</root>

View File

@ -0,0 +1,16 @@
// 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.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace EnvironmentVariables.Telemetry
{
[EventData]
public class EnvironmentVariablesOpenedEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace EnvironmentVariables.Telemetry
{
[EventData]
public class EnvironmentVariablesProfileEnabledEvent : EventBase, IEvent
{
public bool Enabled { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@ -0,0 +1,24 @@
// 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.Diagnostics.Tracing;
using EnvironmentVariables.Models;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace EnvironmentVariables.Telemetry
{
[EventData]
public class EnvironmentVariablesVariableChangedEvent : EventBase, IEvent
{
public VariablesSetType VariablesType { get; set; }
public EnvironmentVariablesVariableChangedEvent(VariablesSetType type)
{
this.VariablesType = type;
}
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@ -0,0 +1,417 @@
// 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.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using EnvironmentVariables.Helpers;
using EnvironmentVariables.Models;
using ManagedCommon;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Dispatching;
namespace EnvironmentVariables.ViewModels
{
public partial class MainViewModel : ObservableObject
{
private readonly IEnvironmentVariablesService _environmentVariablesService;
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
public DefaultVariablesSet UserDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.UserGuid, ResourceLoaderInstance.ResourceLoader.GetString("User"), VariablesSetType.User);
public DefaultVariablesSet SystemDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.SystemGuid, ResourceLoaderInstance.ResourceLoader.GetString("System"), VariablesSetType.System);
public VariablesSet DefaultVariables { get; private set; } = new DefaultVariablesSet(Guid.NewGuid(), "DefaultVariables", VariablesSetType.User);
[ObservableProperty]
private ObservableCollection<ProfileVariablesSet> _profiles;
[ObservableProperty]
private ObservableCollection<Variable> _appliedVariables = new ObservableCollection<Variable>();
[ObservableProperty]
private bool _isElevated;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsInfoBarButtonVisible))]
private EnvironmentState _environmentState;
public bool IsInfoBarButtonVisible => EnvironmentState == EnvironmentState.EnvironmentMessageReceived;
public ProfileVariablesSet AppliedProfile { get; set; }
public MainViewModel(IEnvironmentVariablesService environmentVariablesService)
{
_environmentVariablesService = environmentVariablesService;
var isElevated = App.GetService<IElevationHelper>().IsElevated;
IsElevated = isElevated;
}
private void LoadDefaultVariables()
{
UserDefaultSet.Variables.Clear();
SystemDefaultSet.Variables.Clear();
DefaultVariables.Variables.Clear();
EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.Machine, SystemDefaultSet);
EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.User, UserDefaultSet);
foreach (var variable in UserDefaultSet.Variables)
{
DefaultVariables.Variables.Add(variable);
}
foreach (var variable in SystemDefaultSet.Variables)
{
DefaultVariables.Variables.Add(variable);
}
}
[RelayCommand]
public void LoadEnvironmentVariables()
{
LoadDefaultVariables();
LoadProfiles();
PopulateAppliedVariables();
}
private void LoadProfiles()
{
try
{
var profiles = _environmentVariablesService.ReadProfiles();
foreach (var profile in profiles)
{
profile.PropertyChanged += Profile_PropertyChanged;
foreach (var variable in profile.Variables)
{
variable.ParentType = VariablesSetType.Profile;
}
}
var appliedProfiles = profiles.Where(x => x.IsEnabled).ToList();
if (appliedProfiles.Count > 0)
{
var appliedProfile = appliedProfiles.First();
if (appliedProfile.IsCorrectlyApplied())
{
AppliedProfile = appliedProfile;
EnvironmentState = EnvironmentState.Unchanged;
}
else
{
EnvironmentState = EnvironmentState.ChangedOnStartup;
appliedProfile.IsEnabled = false;
}
}
Profiles = new ObservableCollection<ProfileVariablesSet>(profiles);
}
catch (Exception ex)
{
// Show some error
Logger.LogError("Failed to load profiles.json file", ex);
Profiles = new ObservableCollection<ProfileVariablesSet>();
}
}
private void PopulateAppliedVariables()
{
LoadDefaultVariables();
var variables = new List<Variable>();
if (AppliedProfile != null)
{
variables = variables.Concat(AppliedProfile.Variables.OrderBy(x => x.Name)).ToList();
}
variables = variables.Concat(UserDefaultSet.Variables.OrderBy(x => x.Name)).Concat(SystemDefaultSet.Variables.OrderBy(x => x.Name)).ToList();
// Handle PATH variable - add USER value to the end of the SYSTEM value
var profilePath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.Profile).FirstOrDefault();
var userPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.User).FirstOrDefault();
var systemPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.System).FirstOrDefault();
if (systemPath != null)
{
var clone = systemPath.Clone();
clone.ParentType = VariablesSetType.Path;
if (userPath != null)
{
clone.Values += ";" + userPath.Values;
variables.Remove(userPath);
}
if (profilePath != null)
{
variables.Remove(profilePath);
}
variables.Insert(variables.IndexOf(systemPath), clone);
variables.Remove(systemPath);
}
variables = variables.GroupBy(x => x.Name).Select(y => y.First()).ToList();
// Find duplicates
var duplicates = variables.Where(x => !x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Name.ToLower(CultureInfo.InvariantCulture)).Where(g => g.Count() > 1);
foreach (var duplicate in duplicates)
{
var userVar = duplicate.ElementAt(0);
var systemVar = duplicate.ElementAt(1);
var clone = userVar.Clone();
clone.ParentType = VariablesSetType.Duplicate;
clone.Name = systemVar.Name;
variables.Insert(variables.IndexOf(userVar), clone);
variables.Remove(userVar);
variables.Remove(systemVar);
}
variables = variables.OrderBy(x => x.ParentType).ToList();
AppliedVariables = new ObservableCollection<Variable>(variables);
}
internal void AddDefaultVariable(Variable variable, VariablesSetType type)
{
if (type == VariablesSetType.User)
{
UserDefaultSet.Variables.Add(variable);
UserDefaultSet.Variables = new ObservableCollection<Variable>(UserDefaultSet.Variables.OrderBy(x => x.Name).ToList());
}
else if (type == VariablesSetType.System)
{
SystemDefaultSet.Variables.Add(variable);
SystemDefaultSet.Variables = new ObservableCollection<Variable>(SystemDefaultSet.Variables.OrderBy(x => x.Name).ToList());
}
EnvironmentVariablesHelper.SetVariable(variable);
PopulateAppliedVariables();
}
internal void EditVariable(Variable original, Variable edited, ProfileVariablesSet variablesSet)
{
bool propagateChange = variablesSet == null /* not a profile */ || variablesSet.Id.Equals(AppliedProfile?.Id);
bool changed = original.Name != edited.Name || original.Values != edited.Values;
if (changed)
{
var task = original.Update(edited, propagateChange, variablesSet);
task.ContinueWith(x =>
{
_dispatcherQueue.TryEnqueue(() =>
{
PopulateAppliedVariables();
});
});
PowerToysTelemetry.Log.WriteEvent(new Telemetry.EnvironmentVariablesVariableChangedEvent(original.ParentType));
_ = Task.Run(SaveAsync);
}
}
internal void AddProfile(ProfileVariablesSet profile)
{
profile.PropertyChanged += Profile_PropertyChanged;
if (profile.IsEnabled)
{
UnsetAppliedProfile();
SetAppliedProfile(profile);
}
Profiles.Add(profile);
_ = Task.Run(SaveAsync);
}
internal void UpdateProfile(ProfileVariablesSet updatedProfile)
{
var existingProfile = Profiles.Where(x => x.Id == updatedProfile.Id).FirstOrDefault();
if (existingProfile != null)
{
if (updatedProfile.IsEnabled)
{
// Let's unset the profile before applying the update. Even if this one is the one that's currently set.
UnsetAppliedProfile();
}
existingProfile.Name = updatedProfile.Name;
existingProfile.IsEnabled = updatedProfile.IsEnabled;
existingProfile.Variables = updatedProfile.Variables;
}
_ = Task.Run(SaveAsync);
}
private async Task SaveAsync()
{
try
{
await _environmentVariablesService.WriteAsync(Profiles);
}
catch (Exception ex)
{
// Show some error
Logger.LogError("Failed to save to profiles.json file", ex);
}
}
private void Profile_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var profile = sender as ProfileVariablesSet;
if (profile != null)
{
if (e.PropertyName == nameof(ProfileVariablesSet.IsEnabled))
{
if (profile.IsEnabled)
{
UnsetAppliedProfile();
SetAppliedProfile(profile);
var telemetryEnabled = new Telemetry.EnvironmentVariablesProfileEnabledEvent()
{
Enabled = true,
};
PowerToysTelemetry.Log.WriteEvent(telemetryEnabled);
}
else
{
UnsetAppliedProfile();
var telemetryEnabled = new Telemetry.EnvironmentVariablesProfileEnabledEvent()
{
Enabled = false,
};
PowerToysTelemetry.Log.WriteEvent(telemetryEnabled);
}
}
}
_ = Task.Run(SaveAsync);
}
private void SetAppliedProfile(ProfileVariablesSet profile)
{
if (profile != null)
{
if (!profile.IsApplicable())
{
profile.PropertyChanged -= Profile_PropertyChanged;
profile.IsEnabled = false;
profile.PropertyChanged += Profile_PropertyChanged;
EnvironmentState = EnvironmentState.ProfileNotApplicable;
return;
}
}
var task = profile.Apply();
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
PopulateAppliedVariables();
});
});
AppliedProfile = profile;
}
private void UnsetAppliedProfile()
{
if (AppliedProfile != null)
{
var appliedProfile = AppliedProfile;
appliedProfile.PropertyChanged -= Profile_PropertyChanged;
var task = AppliedProfile.UnApply();
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
PopulateAppliedVariables();
});
});
AppliedProfile.IsEnabled = false;
AppliedProfile = null;
appliedProfile.PropertyChanged += Profile_PropertyChanged;
}
}
internal void RemoveProfile(ProfileVariablesSet profile)
{
if (profile.IsEnabled)
{
UnsetAppliedProfile();
}
Profiles.Remove(profile);
_ = Task.Run(SaveAsync);
}
internal void DeleteVariable(Variable variable, ProfileVariablesSet profile)
{
bool propagateChange = true;
if (profile != null)
{
// Profile variable
profile.Variables.Remove(variable);
if (!profile.IsEnabled)
{
propagateChange = false;
}
_ = Task.Run(SaveAsync);
}
else
{
if (variable.ParentType == VariablesSetType.User)
{
UserDefaultSet.Variables.Remove(variable);
}
else if (variable.ParentType == VariablesSetType.System)
{
SystemDefaultSet.Variables.Remove(variable);
}
}
if (propagateChange)
{
var task = Task.Run(() =>
{
if (profile == null)
{
EnvironmentVariablesHelper.UnsetVariable(variable);
}
else
{
profile.UnapplyVariable(variable);
}
});
task.ContinueWith((a) =>
{
_dispatcherQueue.TryEnqueue(() =>
{
PopulateAppliedVariables();
});
});
}
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="EnvironmentVariables.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
For more info see https://docs.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
It is also necessary to support features in unpackaged applications, for example the custom title bar implementation.-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@ -0,0 +1,40 @@
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h EnvironmentVariablesModuleInterface.base.rc EnvironmentVariablesModuleInterface.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{b9420661-b0e4-4241-abd4-4a27a1f64250}</ProjectGuid>
<RootNamespace>EnvironmentVariablesModuleInterface</RootNamespace>
<ProjectName>EnvironmentVariablesModuleInterface</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.EnvironmentVariablesModuleInterface</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)src;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<None Include="resource.base.h" />
<ClInclude Include="trace.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="trace.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="EnvironmentVariablesModuleInterface.base.rc" />
<ResourceCompile Include="Generated Files\EnvironmentVariablesModuleInterface.rc" />
</ItemGroup>
<ItemGroup>
<None Include="Resource.resx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="resource.base.h">
<Filter>Header Files</Filter>
</None>
<None Include="Resource.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="EnvironmentVariablesModuleInterface.base.rc">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files\EnvironmentVariablesModuleInterface.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Environment_Variables_Name" xml:space="preserve">
<value>Environment Variables</value>
</data>
</root>

View File

@ -0,0 +1,280 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "Generated Files/resource.h"
#include "trace.h"
#include <common/interop/shared_constants.h>
#include <common/logger/logger.h>
#include <common/utils/EventWaiter.h>
#include <common/utils/gpo.h>
#include <common/utils/logger_helper.h>
#include <common/utils/resources.h>
#include <common/utils/winapi_error.h>
#include <interface/powertoy_module_interface.h>
#include <shellapi.h>
extern "C" IMAGE_DOS_HEADER __ImageBase;
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;
}
namespace
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"EnvironmentVariables";
}
class EnvironmentVariablesModuleInterface : public PowertoyModuleIface
{
private:
bool m_enabled = false;
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
HANDLE m_hProcess;
HANDLE m_hShowEvent;
EventWaiter m_showEventWaiter;
HANDLE m_hShowAdminEvent;
EventWaiter m_showAdminEventWaiter;
bool is_process_running()
{
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void bring_process_to_front()
{
auto enum_windows = [](HWND hwnd, LPARAM param) -> BOOL {
HANDLE process_handle = reinterpret_cast<HANDLE>(param);
DWORD window_process_id = 0;
GetWindowThreadProcessId(hwnd, &window_process_id);
if (GetProcessId(process_handle) == window_process_id)
{
SetForegroundWindow(hwnd);
return FALSE;
}
return TRUE;
};
EnumWindows(enum_windows, (LPARAM)m_hProcess);
}
void launch_process(bool runas)
{
Logger::trace("EnvironmentVariablesModuleInterface::launch_process()");
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"WinUI3Apps\\PowerToys.EnvironmentVariables.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (runas)
{
sei.lpVerb = L"runas";
}
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started the Environment Variables process");
}
else
{
Logger::error(L"Environment Variables failed to start. {}", get_last_error_or_default(GetLastError()));
}
m_hProcess = sei.hProcess;
}
public:
EnvironmentVariablesModuleInterface()
{
app_name = GET_RESOURCE_STRING(IDS_ENVIRONMENT_VARIABLES_NAME);
app_key = ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::environmentVariablesLoggerName);
m_hShowEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT);
if (!m_hShowEvent)
{
Logger::error(L"Failed to create show Environment Variables event");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
m_hShowAdminEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT);
if (!m_hShowAdminEvent)
{
Logger::error(L"Failed to create show Environment Variables admin event");
auto message = get_last_error_message(GetLastError());
if (message.has_value())
{
Logger::error(message.value());
}
}
m_showEventWaiter = EventWaiter(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT, [&](int err) {
if (m_enabled && err == ERROR_SUCCESS)
{
Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT);
if (is_process_running())
{
bring_process_to_front();
}
else
{
launch_process(false);
}
Trace::ActivateEnvironmentVariables();
}
});
m_showAdminEventWaiter = EventWaiter(CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT, [&](int err) {
if (m_enabled && err == ERROR_SUCCESS)
{
Logger::trace(L"{} event was signaled", CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT);
if (is_process_running())
{
bring_process_to_front();
}
else
{
launch_process(true);
}
Trace::ActivateEnvironmentVariables();
}
});
}
~EnvironmentVariablesModuleInterface()
{
m_enabled = false;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Logger::trace("EnvironmentVariablesModuleInterface::destroy()");
if (m_hShowEvent)
{
CloseHandle(m_hShowEvent);
m_hShowEvent = nullptr;
}
if (m_hShowAdminEvent)
{
CloseHandle(m_hShowAdminEvent);
m_hShowAdminEvent = nullptr;
}
delete this;
}
// Return the localized display name of the powertoy
virtual const wchar_t* get_name() override
{
return app_name.c_str();
}
// 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();
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredEnvironmentVariablesEnabledValue();
}
virtual bool get_config(wchar_t* /*buffer*/, int* /*buffer_size*/) override
{
return false;
}
virtual void call_custom_action(const wchar_t* /*action*/) override
{
}
virtual void set_config(const wchar_t* /*config*/) override
{
}
virtual bool is_enabled() override
{
return m_enabled;
}
virtual void enable()
{
Logger::trace("EnvironmentVariablesModuleInterface::enable()");
m_enabled = true;
Trace::EnableEnvironmentVariables(true);
}
virtual void disable()
{
Logger::trace("EnvironmentVariablesModuleInterface::disable()");
if (m_enabled)
{
if (m_hShowEvent)
{
ResetEvent(m_hShowEvent);
}
if (m_hShowAdminEvent)
{
ResetEvent(m_hShowAdminEvent);
}
TerminateProcess(m_hProcess, 1);
}
m_enabled = false;
Trace::EnableEnvironmentVariables(false);
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface * __cdecl powertoy_create()
{
return new EnvironmentVariablesModuleInterface();
}

View File

@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -0,0 +1,18 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <ProjectTelemetry.h>
#include <optional>
#include <string>
#endif //PCH_H

View File

@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by AlwaysOnTopModuleInterface.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys Environment Variables Module"
#define INTERNAL_NAME "PowerToys.EnvironmentVariablesModuleInterface"
#define ORIGINAL_FILENAME "PowerToys.EnvironmentVariablesModuleInterface.dll"
// Non-localizable
//////////////////////////////

View File

@ -0,0 +1,40 @@
#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() noexcept
{
TraceLoggingRegister(g_hProvider);
}
void Trace::UnregisterProvider() noexcept
{
TraceLoggingUnregister(g_hProvider);
}
// Log if the user has Environment Variables enabled or disabled
void Trace::EnableEnvironmentVariables(const bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
"EnvironmentVariables_EnableEnvironmentVariables",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log that the user tried to activate the editor
void Trace::ActivateEnvironmentVariables() noexcept
{
TraceLoggingWrite(
g_hProvider,
"EnvironmentVariables_Activate",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}

View File

@ -0,0 +1,14 @@
#pragma once
class Trace
{
public:
static void RegisterProvider() noexcept;
static void UnregisterProvider() noexcept;
// Log if the user has EnvironmentVariables enabled or disabled
static void EnableEnvironmentVariables(const bool enabled) noexcept;
// Log that the user tried to activate the editor
static void ActivateEnvironmentVariables() noexcept;
};

View File

@ -168,6 +168,13 @@ public:
{
return m_enabled;
}
// Returns whether the PowerToys should be enabled by default
virtual bool is_enabled_by_default() const override
{
return false;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@ -61,11 +61,25 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowHostsAdminSharedEvent()))
{
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowHostsAdminSharedEvent());
eventHandle.Set();
return true;
},
});
}
else if (Key == UtilityKey.EnvironmentVariables)
{
results.Add(new ContextMenuResult
{
Title = Resources.Action_Run_As_Administrator,
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = System.Windows.Input.Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowEnvironmentVariablesAdminSharedEvent());
eventHandle.Set();
return true;
},
});

View File

@ -20,6 +20,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
UtilityKey.ShortcutGuide => "Images/ShortcutGuide.png",
UtilityKey.RegistryPreview => "Images/RegistryPreview.png",
UtilityKey.CropAndLock => "Images/CropAndLock.png",
UtilityKey.EnvironmentVariables => "Images/EnvironmentVariables.png",
_ => null,
};
}
@ -36,6 +37,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
UtilityKey.ShortcutGuide => SettingsDeepLink.SettingsWindow.ShortcutGuide,
UtilityKey.RegistryPreview => SettingsDeepLink.SettingsWindow.RegistryPreview,
UtilityKey.CropAndLock => SettingsDeepLink.SettingsWindow.CropAndLock,
UtilityKey.EnvironmentVariables => SettingsDeepLink.SettingsWindow.EnvironmentVariables,
_ => null,
};
}

View File

@ -14,5 +14,6 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
ShortcutGuide = 5,
RegistryPreview = 6,
CropAndLock = 7,
EnvironmentVariables = 8,
}
}

View File

@ -170,6 +170,20 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
}));
}
if (GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue() != GpoRuleConfigured.Disabled)
{
_utilities.Add(new Utility(
UtilityKey.EnvironmentVariables,
Resources.Environment_Variables,
generalSettings.Enabled.EnvironmentVariables,
(_) =>
{
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowEnvironmentVariablesSharedEvent());
eventHandle.Set();
return true;
}));
}
_watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(settingsUtils.GetSettingsFilePath()),
@ -214,6 +228,7 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys
case UtilityKey.ShortcutGuide: u.Enable(generalSettings.Enabled.ShortcutGuide); break;
case UtilityKey.RegistryPreview: u.Enable(generalSettings.Enabled.RegistryPreview); break;
case UtilityKey.CropAndLock: u.Enable(generalSettings.Enabled.CropAndLock); break;
case UtilityKey.EnvironmentVariables: u.Enable(generalSettings.Enabled.EnvironmentVariables); break;
}
}

View File

@ -105,6 +105,10 @@
<Link>Images\ShortcutGuide.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\..\..\..\settings-ui\Settings.UI\Assets\Settings\FluentIcons\FluentIconsEnvironmentVariables.png">
<Link>Images\EnvironmentVariables.png</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -105,6 +105,15 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Environment Variables.
/// </summary>
internal static string Environment_Variables {
get {
return ResourceManager.GetString("Environment_Variables", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FancyZones Editor.
/// </summary>

View File

@ -135,6 +135,10 @@
<value>Crop And Lock (Thumbnail)</value>
<comment>"Crop And Lock" is the name of the utility, "Thumbnail" is the activation mode</comment>
</data>
<data name="Environment_Variables" xml:space="preserve">
<value>Environment Variables</value>
<comment>"Environment Variables" is the name of the utility</comment>
</data>
<data name="FancyZones_Editor" xml:space="preserve">
<value>FancyZones Editor</value>
<comment>"FancyZones" is the name of the utility</comment>

View File

@ -774,7 +774,19 @@ namespace RegistryPreview
// before moving onto the next node, tag the previous node and update the path
previousNode = newNode;
fullPath = fullPath.Replace(string.Format(CultureInfo.InvariantCulture, @"\{0}", individualKeys[i]), string.Empty);
// this used to use Replace but that would replace all instances of the same key name, which causes bugs.
try
{
int removeAt = fullPath.LastIndexOf(string.Format(CultureInfo.InvariantCulture, @"\{0}", individualKeys[i]), StringComparison.InvariantCulture);
if (removeAt > -1)
{
fullPath = fullPath.Substring(0, removeAt);
}
}
catch
{
}
// One last check: if we get here, the parent of this node is not yet in the tree, so we need to add it as a RootNode
if (i == 0)

View File

@ -81,7 +81,7 @@ inline wil::unique_mutex_nothrow create_msi_mutex()
void open_menu_from_another_instance(std::optional<std::string> settings_window)
{
const HWND hwnd_main = FindWindowW(L"PToyTrayIconWindow", nullptr);
LPARAM msg = static_cast<LPARAM>(ESettingsWindowNames::Overview);
LPARAM msg = static_cast<LPARAM>(ESettingsWindowNames::Dashboard);
if (settings_window.has_value() && settings_window.value() != "")
{
msg = static_cast<LPARAM>(ESettingsWindowNames_from_string(settings_window.value()));
@ -155,6 +155,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"WinUI3Apps/PowerToys.MeasureToolModuleInterface.dll",
L"WinUI3Apps/PowerToys.HostsModuleInterface.dll",
L"WinUI3Apps/PowerToys.Peek.dll",
L"WinUI3Apps/PowerToys.EnvironmentVariablesModuleInterface.dll",
L"PowerToys.MouseWithoutBordersModuleInterface.dll",
L"PowerToys.CropAndLockModuleInterface.dll",
};

View File

@ -596,7 +596,7 @@ void open_settings_window(std::optional<std::wstring> settings_window, bool show
}
else
{
current_settings_ipc->send(L"{\"ShowYourself\":\"Overview\"}");
current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
}
}
}
@ -676,6 +676,10 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
return "RegistryPreview";
case ESettingsWindowNames::CropAndLock:
return "CropAndLock";
case ESettingsWindowNames::EnvironmentVariables:
return "EnvironmentVariables";
case ESettingsWindowNames::Dashboard:
return "Dashboard";
default:
{
Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast<int>(value));
@ -755,11 +759,19 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
{
return ESettingsWindowNames::CropAndLock;
}
else if (value == "EnvironmentVariables")
{
return ESettingsWindowNames::EnvironmentVariables;
}
else if (value == "Dashboard")
{
return ESettingsWindowNames::Dashboard;
}
else
{
Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value));
assert(false);
}
return ESettingsWindowNames::Overview;
return ESettingsWindowNames::Dashboard;
}

View File

@ -4,7 +4,8 @@
enum class ESettingsWindowNames
{
Overview = 0,
Dashboard = 0,
Overview,
Awake,
ColorPicker,
FancyZones,
@ -21,6 +22,7 @@ enum class ESettingsWindowNames
PowerOCR,
RegistryPreview,
CropAndLock,
EnvironmentVariables,
};
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);

View File

@ -427,6 +427,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
private bool environmentVariables = true;
[JsonPropertyName("EnvironmentVariables")]
public bool EnvironmentVariables
{
get => environmentVariables;
set
{
if (environmentVariables != value)
{
LogTelemetryEvent(value);
environmentVariables = value;
}
}
}
private void NotifyChange()
{
notifyEnabledChangedAction?.Invoke();

View File

@ -0,0 +1,20 @@
// 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 Settings.UI.Library.Enumerations;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class EnvironmentVariablesProperties
{
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool LaunchAdministrator { get; set; }
public EnvironmentVariablesProperties()
{
LaunchAdministrator = true;
}
}
}

View File

@ -0,0 +1,46 @@
// 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.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class EnvironmentVariablesSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "EnvironmentVariables";
[JsonPropertyName("properties")]
public EnvironmentVariablesProperties Properties { get; set; }
public EnvironmentVariablesSettings()
{
Properties = new EnvironmentVariablesProperties();
Version = "1.0";
Name = ModuleName;
}
public virtual void Save(ISettingsUtils settingsUtils)
{
// Save settings to file
var options = new JsonSerializerOptions
{
WriteIndented = true,
};
if (settingsUtils == null)
{
throw new ArgumentNullException(nameof(settingsUtils));
}
settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName);
}
public string GetModuleName() => Name;
public bool UpgradeSettingsConfiguration() => false;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

View File

@ -0,0 +1,33 @@
// 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.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public class ModuleItemTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate ButtonTemplate { get; set; }
public DataTemplate ShortcutTemplate { get; set; }
public DataTemplate KBMTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
case DashboardModuleButtonItem: return ButtonTemplate;
case DashboardModuleShortcutItem: return ShortcutTemplate;
case DashboardModuleTextItem: return TextTemplate;
case DashboardModuleKBMItem: return KBMTemplate;
default: return TextTemplate;
}
}
}
}

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;
using System.Globalization;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public class NegativeBoolToVisibilityConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, string language)
{
if ((bool)value)
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility)
{
if ((Visibility)value == Visibility.Visible)
{
return false;
}
}
return true;
}
}
}

View File

@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
Awake,
ColorPicker,
CropAndLock,
EnvironmentVariables,
FancyZones,
FileLocksmith,
FileExplorer,

View File

@ -21,6 +21,9 @@
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.Settings.pri</ProjectPriFileName>
</PropertyGroup>
<ItemGroup>
<None Remove="SettingsXAML\Views\DashboardPage.xaml" />
</ItemGroup>
<ItemGroup>
<Page Remove="SettingsXAML\App.xaml" />
@ -125,4 +128,10 @@
</None>
</ItemGroup>
<ItemGroup>
<Page Update="SettingsXAML\Views\DashboardPage.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
</Project>

View File

@ -60,7 +60,7 @@ namespace Microsoft.PowerToys.Settings.UI
public bool ShowScoobe { get; set; }
public Type StartupPage { get; set; } = typeof(Views.GeneralPage);
public Type StartupPage { get; set; } = typeof(Views.DashboardPage);
public static Action<string> IPCMessageReceivedCallback { get; set; }
@ -218,8 +218,8 @@ namespace Microsoft.PowerToys.Settings.UI
settingsWindow.NavigateToSection(StartupPage);
ShowMessageDialog("The application is running in Debug mode.", "DEBUG");
#else
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the General page. */
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Overview, true);
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Dashboard, true);
Exit();
#endif
}
@ -380,6 +380,7 @@ namespace Microsoft.PowerToys.Settings.UI
{
switch (settingWindow)
{
case "Dashboard": return typeof(DashboardPage);
case "Overview": return typeof(GeneralPage);
case "AlwaysOnTop": return typeof(AlwaysOnTopPage);
case "Awake": return typeof(AwakePage);
@ -403,10 +404,11 @@ namespace Microsoft.PowerToys.Settings.UI
case "PastePlain": return typeof(PastePlainPage);
case "Peek": return typeof(PeekPage);
case "CropAndLock": return typeof(CropAndLockPage);
case "EnvironmentVariables": return typeof(EnvironmentVariablesPage);
default:
// Fallback to general
// Fallback to Dashboard
Debug.Assert(false, "Unexpected SettingsWindow argument value");
return typeof(GeneralPage);
return typeof(DashboardPage);
}
}
}

View File

@ -116,7 +116,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
case 91: // The left Windows key
case 92: // The right Windows key
PathIcon winIcon = XamlReader.Load(@"<PathIcon xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""M9,17V9h8v8ZM0,17V9H8v8ZM9,8V0h8V8ZM0,8V0H8V8Z"" />") as PathIcon;
PathIcon winIcon = XamlReader.Load(@"<PathIcon xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" Data=""M683 1229H0V546h683v683zm819 0H819V546h683v683zm-819 819H0v-683h683v683zm819 0H819v-683h683v683z"" />") as PathIcon;
Viewbox winIconContainer = new Viewbox();
winIconContainer.Child = winIcon;
winIconContainer.HorizontalAlignment = HorizontalAlignment.Center;
@ -143,6 +143,10 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
return (Style)App.Current.Resources["SmallOutline" + styleName];
}
else if (VisualType == VisualType.TextOnly)
{
return (Style)App.Current.Resources["Only" + styleName];
}
else
{
return (Style)App.Current.Resources["Default" + styleName];
@ -181,6 +185,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
Small,
SmallOutline,
TextOnly,
Large,
}
}

View File

@ -5,9 +5,7 @@
<x:Double x:Key="DefaultIconSize">16</x:Double>
<x:Double x:Key="SmallIconSize">12</x:Double>
<Style
x:Key="DefaultTextKeyVisualStyle"
TargetType="local:KeyVisual">
<Style x:Key="DefaultTextKeyVisualStyle" TargetType="local:KeyVisual">
<Setter Property="MinWidth" Value="56" />
<Setter Property="MinHeight" Value="48" />
<Setter Property="Background" Value="{ThemeResource AccentButtonBackground}" />
@ -23,26 +21,6 @@
<Setter.Value>
<ControlTemplate TargetType="local:KeyVisual">
<Grid>
<Grid>
<Rectangle
x:Name="ContentHolder"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
Fill="{TemplateBinding Background}"
RadiusX="4"
RadiusY="4"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}" />
<ContentPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
@ -67,6 +45,26 @@
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Rectangle
x:Name="ContentHolder"
Height="{TemplateBinding Height}"
MinWidth="{TemplateBinding MinWidth}"
Fill="{TemplateBinding Background}"
RadiusX="4"
RadiusY="4"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}" />
<ContentPresenter
x:Name="KeyPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
@ -101,6 +99,7 @@
</Style>
<Style
x:Key="DefaultIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
@ -141,4 +140,35 @@
<Setter Property="FontSize" Value="9" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="OnlyTextKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="MinHeight" Value="12" />
<Setter Property="MinWidth" Value="12" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style
x:Key="OnlyIconKeyVisualStyle"
BasedOn="{StaticResource DefaultTextKeyVisualStyle}"
TargetType="local:KeyVisual">
<Setter Property="MinHeight" Value="10" />
<Setter Property="MinWidth" Value="10" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Padding" Value="0,0,0,3" />
<!--<Setter Property="FontSize" Value="9" />-->
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</ResourceDictionary>

Some files were not shown because too many files have changed in this diff Show More