🚧 [new module] Environment variables editor (#28722)
* Init EnvironmentVariables UI project * Models TitleBar MainPage init Icon * User and system variables * Profiles init * XAML cleanup * Missing ItemTemplate * EditDialog * ModuleInterface * Signing and processes lists * Installer * spellcheck * Fix ARM64 build and consolidate packages * spellcheck2 * Fix installer * Single instance. C# telemetry. Wait on PT pid * ElevationHelper * Add profile wip * Init EnvironmentVariables UI project * Models TitleBar MainPage init Icon * User and system variables * Profiles init * XAML cleanup * Missing ItemTemplate * EditDialog * ModuleInterface * Signing and processes lists * Installer * spellcheck * Fix ARM64 build and consolidate packages * spellcheck2 * Fix installer * Single instance. C# telemetry. Wait on PT pid * ElevationHelper * Add profile wip * show run as administrator in title (#28516) * Environment Variables added to Run plugin (#28466) * UI tweaks * Remove style * Add profile - init working * Applied variables * Read/Write profiles * Fixes * Add separator and fix loading profiles * Only allow to edit System vars if running elevated * Add tmp progress ring to show applying changes progress Ignore not needed json fields * Remove variable and profile logic * Do not read data async Update System and User variables on change * Add isCorrectlyApplied() * Sort variables in Applied variables * WIP WndProc * spellcheck * Revert "WIP WndProc" This reverts commit0c0b6c67de
. * WHY CRASH??? * UI tweaks * WIP modified state warning * Add cancel button in dialogs * Add buttons validations * Set variables - fire and forget notify * Revert "Revert "WIP WndProc"" This reverts commit1b2306eeb7
. * Listen to WM_SETTINGSCHANGED Add Infobar reload button * spellcheck * spellcheck again * Fix build * InfoBar runAsAdmin visibility * Fix comment * Confirm dialog when deleting variable Fix add variable button when creating profile * Edit profile * Sort variables on Load * Select existing variables on edit * Add default variable * Fix adding existing vars to profile * update notice.md * Handle PATH properly * Add tooltips and fix dialogs text wrapping * Fix applied values for duplicates Fix add/eddit variable txt box validation * Add horizontal scroll bar for applied values * Fix duplicate variables handling Fix user variable handling and preview * spellcheck * Try fix spellcheck * Revert "spellcheck" This reverts commitee76231974
. * Revert "Try fix spellcheck" This reverts commitdc8f04afb9
. * Fix path and duplicates conflict * Fix PATH handling Fix unapply on delete active variable Fix ordering in applied variables * Show variables as lists and add drag-to-reorder feature * Only show specific variables as list Update list in edit dialog on editing the value * spellcheck * Update GPO policy * Add Edit and Remove variable buttons Remove context menu * Remove drag&drop when editing list variable and add buttons to move up/down * Fix Edit profile dialog title * Fix backup and restore variables when editing variable from applied profile * Apply var to system WIP * Tweaks * Simplify edit variable logic * Minor fixes * Spellcheck * Update src/modules/EnvironmentVariables/EnvironmentVariables/app.manifest Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> * spellcheck 2 * Remove unneeded string * Add more telemetry * Do not allow adding existing variables with the same name into the profile * Adding icon * Fix the crash when opening existing variables dialog * Update Settings and OOBE screenshots * Fix crash when malformed profiles.json and jsonignore not needed properties * Fix selecting duplicates in existing variables dialog * Add user variable name limit (<255 chars) Check if profile is applicable on apply Show message if profile is not applicable * XamlStyling * Better Flyout positioning Add Cancel button to the flyout * Fix UI glitches by using ItemsControl (no virtualization) * Fix spellcheck * Fix XAML style * Add horizontal scrollbar to applied variables * Revert to ItemsRepeater * Fix UI glitches by adding a decent minimum cache * Fixing UI bugs * Fix spellcheck * Fix crash while trying to edit a User variable when there's no Parent profile * Fix issue overwriting backup var when you edit var on applied profile * Fix nuking of variables when adding to applied profile * Fix profile not being saved when deleting a variable * Fix ValuesList empty crash, issues and no serialization * fix spellcheck * Allow in-line edit of list variables * Fix xaml style * Fix add profile variable cancel button logic * Fix add profile variable cancel button logic - clean the list * Bump VerticalCacheLength to 10 as in some cases UI glitch on expanding System variables was still present * Fix profile Add variable button enable/disable logic * Remove unneeded using * Add to Dashboard --------- Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Davide Giacometti <davide.giacometti@outlook.it> Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -313,6 +313,7 @@ SOFTWARE.
|
|||
- CommunityToolkit.WinUI.Animations 8.0.230907
|
||||
- CommunityToolkit.WinUI.Collections 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
|
||||
|
|
|
@ -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}
|
||||
|
|
После Ширина: | Высота: | Размер: 27 KiB |
|
@ -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>
|
|
@ -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" />
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
<ComponentGroupRef Id="ShortcutGuideComponentGroup" />
|
||||
<ComponentGroupRef Id="VideoConferenceComponentGroup" />
|
||||
<ComponentGroupRef Id="MouseWithoutBordersComponentGroup" />
|
||||
<ComponentGroupRef Id="EnvironmentVariablesComponentGroup" />
|
||||
|
||||
<ComponentGroupRef Id="ResourcesComponentGroup" />
|
||||
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
||||
|
|
|
@ -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""""
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace Common.UI
|
|||
PowerOCR,
|
||||
RegistryPreview,
|
||||
CropAndLock,
|
||||
EnvironmentVariables,
|
||||
Dashboard,
|
||||
}
|
||||
|
||||
|
@ -69,6 +70,8 @@ namespace Common.UI
|
|||
return "RegistryPreview";
|
||||
case SettingsWindow.CropAndLock:
|
||||
return "CropAndLock";
|
||||
case SettingsWindow.EnvironmentVariables:
|
||||
return "EnvironmentVariables";
|
||||
case SettingsWindow.Dashboard:
|
||||
return "Dashboard";
|
||||
default:
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace PowerToys
|
|||
static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue();
|
||||
static GpoRuleConfigured GetAllowExperimentationValue();
|
||||
static GpoRuleConfigured GetRunPluginEnabledValue(String pluginID);
|
||||
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/EnvironmentVariables.ico
Normal file
После Ширина: | Высота: | Размер: 460 KiB |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/LockScreenLogo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 432 B |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/ProfileIcon.png
Normal file
После Ширина: | Высота: | Размер: 432 B |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/SplashScreen.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 5.2 KiB |
После Ширина: | Высота: | Размер: 1.7 KiB |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/Square44x44Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 637 B |
После Ширина: | Высота: | Размер: 283 B |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/StoreLogo.png
Normal file
После Ширина: | Высота: | Размер: 456 B |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/SystemIcon.png
Normal file
После Ширина: | Высота: | Размер: 5.4 KiB |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/UserIcon.png
Normal file
После Ширина: | Высота: | Размер: 432 B |
Двоичные данные
src/modules/EnvironmentVariables/EnvironmentVariables/Assets/EnvironmentVariables/Wide310x150Logo.scale-200.png
Normal file
После Ширина: | Высота: | Размер: 2.0 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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=}" Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="EditItem"
|
||||
Click="EditVariable_Click"
|
||||
CommandParameter="{x:Bind (models:Variable)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveItem"
|
||||
Click="Delete_Variable_Click"
|
||||
CommandParameter="{x:Bind (models:Variable)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</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="" />
|
||||
<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=}"
|
||||
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="">
|
||||
<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=}">
|
||||
<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=}" Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="EditItem"
|
||||
Click="EditProfileBtn_Click"
|
||||
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveItem"
|
||||
Click="RemoveProfileBtn_Click"
|
||||
CommandParameter="{x:Bind (models:ProfileVariablesSet)}"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</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="">
|
||||
<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=}">
|
||||
<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=}">
|
||||
<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="" />
|
||||
</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="">
|
||||
<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=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveUp"
|
||||
Click="ReorderButtonUp_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveDown"
|
||||
Click="ReorderButtonDown_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveListItem"
|
||||
Click="RemoveListVariableButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="InsertListEntryBefore"
|
||||
Click="InsertListEntryBeforeButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="InsertListEntryAfter"
|
||||
Click="InsertListEntryAfterButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}" />
|
||||
</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="" />
|
||||
<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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
||||
//////////////////////////////
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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()))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,5 +14,6 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys.Components
|
|||
ShortcutGuide = 5,
|
||||
RegistryPreview = 6,
|
||||
CropAndLock = 7,
|
||||
EnvironmentVariables = 8,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
|
|
@ -676,6 +676,8 @@ 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:
|
||||
|
@ -757,6 +759,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
|||
{
|
||||
return ESettingsWindowNames::CropAndLock;
|
||||
}
|
||||
else if (value == "EnvironmentVariables")
|
||||
{
|
||||
return ESettingsWindowNames::EnvironmentVariables;
|
||||
}
|
||||
else if (value == "Dashboard")
|
||||
{
|
||||
return ESettingsWindowNames::Dashboard;
|
||||
|
|
|
@ -22,6 +22,7 @@ enum class ESettingsWindowNames
|
|||
PowerOCR,
|
||||
RegistryPreview,
|
||||
CropAndLock,
|
||||
EnvironmentVariables,
|
||||
};
|
||||
|
||||
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Двоичные данные
src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsEnvironmentVariables.png
Normal file
После Ширина: | Высота: | Размер: 2.1 KiB |
Двоичные данные
src/settings-ui/Settings.UI/Assets/Settings/Modules/EnvironmentVariables.png
Normal file
После Ширина: | Высота: | Размер: 94 KiB |
Двоичные данные
src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/EnvironmentVariables.png
Normal file
После Ширина: | Высота: | Размер: 298 KiB |
|
@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
|||
Awake,
|
||||
ColorPicker,
|
||||
CropAndLock,
|
||||
EnvironmentVariables,
|
||||
FancyZones,
|
||||
FileLocksmith,
|
||||
FileExplorer,
|
||||
|
|
|
@ -134,4 +134,4 @@
|
|||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -404,6 +404,7 @@ 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 Dashboard
|
||||
Debug.Assert(false, "Unexpected SettingsWindow argument value");
|
||||
|
|
|
@ -42,6 +42,21 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
|
|||
}
|
||||
|
||||
break;
|
||||
case "EnvironmentVariables": // Launch Environment Variables
|
||||
{
|
||||
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
|
||||
string eventName = !App.IsElevated && launchAdmin
|
||||
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
|
||||
: Constants.ShowEnvironmentVariablesSharedEvent();
|
||||
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "FancyZones": // Launch FancyZones Editor
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent()))
|
||||
{
|
||||
|
|
|
@ -100,6 +100,9 @@ namespace Microsoft.PowerToys.Settings.UI
|
|||
case "CropAndLock":
|
||||
needToUpdate = generalSettingsConfig.Enabled.CropAndLock != isEnabled;
|
||||
generalSettingsConfig.Enabled.CropAndLock = isEnabled; break;
|
||||
case "EnvironmentVariables":
|
||||
needToUpdate = generalSettingsConfig.Enabled.EnvironmentVariables != isEnabled;
|
||||
generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break;
|
||||
case "FancyZones":
|
||||
needToUpdate = generalSettingsConfig.Enabled.FancyZones != isEnabled;
|
||||
generalSettingsConfig.Enabled.FancyZones = isEnabled; break;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeEnvironmentVariables"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:toolkitcontrols="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_EnvironmentVariables" HeroImage="ms-appx:///Assets/Settings/Modules/OOBE/EnvironmentVariables.png">
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel
|
||||
Margin="0,24,0,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<Button
|
||||
x:Uid="Launch_EnvironmentVariables"
|
||||
Click="Launch_EnvironmentVariables_Click"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button x:Uid="OOBE_Settings" Click="Launch_Settings_Click" />
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_EnvironmentVariables" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_EnvironmentVariables" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
|
@ -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.Threading;
|
||||
using interop;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
public sealed partial class OobeEnvironmentVariables : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; }
|
||||
|
||||
public OobeEnvironmentVariables()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.EnvironmentVariables]);
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogOpeningModuleEvent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogClosingModuleEvent();
|
||||
}
|
||||
|
||||
private void Launch_EnvironmentVariables_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
bool launchAdmin = SettingsRepository<EnvironmentVariablesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
|
||||
string eventName = !App.IsElevated && launchAdmin
|
||||
? Constants.ShowEnvironmentVariablesAdminSharedEvent()
|
||||
: Constants.ShowEnvironmentVariablesSharedEvent();
|
||||
|
||||
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
|
||||
{
|
||||
eventHandle.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void Launch_Settings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(EnvironmentVariablesPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<UserControl
|
||||
<UserControl
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeShellPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
|
@ -14,24 +14,6 @@
|
|||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LayoutVisualStates">
|
||||
<VisualState x:Name="WideLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="720" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SmallLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="600" />
|
||||
<AdaptiveTrigger MinWindowWidth="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="navigationView.PaneDisplayMode" Value="LeftMinimal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Button
|
||||
x:Name="PaneToggleBtn"
|
||||
Width="48"
|
||||
|
@ -64,11 +46,11 @@
|
|||
</StackPanel>
|
||||
</Grid>
|
||||
<NavigationView
|
||||
Grid.Row="1"
|
||||
x:Name="navigationView"
|
||||
Grid.Row="1"
|
||||
DisplayModeChanged="NavigationView_DisplayModeChanged"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsPaneOpen="True"
|
||||
DisplayModeChanged="NavigationView_DisplayModeChanged"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
OpenPaneLength="296"
|
||||
|
@ -95,6 +77,10 @@
|
|||
x:Uid="Shell_CropAndLock"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/FluentIcons/FluentIconsCropAndLock.png}"
|
||||
Tag="CropAndLock" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_EnvironmentVariables"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/FluentIcons/FluentIconsEnvironmentVariables.png}"
|
||||
Tag="EnvironmentVariables" />
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_FancyZones"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/FluentIcons/FluentIconsFancyZones.png}"
|
||||
|
@ -171,13 +157,30 @@
|
|||
<NavigationView.FooterMenuItems>
|
||||
<NavigationViewItem
|
||||
x:Uid="Shell_WhatsNew"
|
||||
Icon="{ui:FontIcon
|
||||
Glyph=}"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="WhatsNew" />
|
||||
</NavigationView.FooterMenuItems>
|
||||
<NavigationView.Content>
|
||||
<Frame x:Name="NavigationFrame" />
|
||||
</NavigationView.Content>
|
||||
</NavigationView>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="LayoutVisualStates">
|
||||
<VisualState x:Name="WideLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="720" />
|
||||
</VisualState.StateTriggers>
|
||||
</VisualState>
|
||||
<VisualState x:Name="SmallLayout">
|
||||
<VisualState.StateTriggers>
|
||||
<AdaptiveTrigger MinWindowWidth="600" />
|
||||
<AdaptiveTrigger MinWindowWidth="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="navigationView.PaneDisplayMode" Value="LeftMinimal" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</UserControl>
|
|
@ -89,6 +89,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
|||
ModuleName = "CropAndLock",
|
||||
IsNew = true,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.EnvironmentVariables, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "EnvironmentVariables",
|
||||
IsNew = true,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.FancyZones, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "FancyZones",
|
||||
|
@ -247,6 +252,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
|||
case "Awake": NavigationFrame.Navigate(typeof(OobeAwake)); break;
|
||||
case "ColorPicker": NavigationFrame.Navigate(typeof(OobeColorPicker)); break;
|
||||
case "CropAndLock": NavigationFrame.Navigate(typeof(OobeCropAndLock)); break;
|
||||
case "EnvironmentVariables": NavigationFrame.Navigate(typeof(OobeEnvironmentVariables)); break;
|
||||
case "FancyZones": NavigationFrame.Navigate(typeof(OobeFancyZones)); break;
|
||||
case "FileLocksmith": NavigationFrame.Navigate(typeof(OobeFileLocksmith)); break;
|
||||
case "Run": NavigationFrame.Navigate(typeof(OobeRun)); break;
|
||||
|
|