🚧 [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 commit 0c0b6c67de.

* 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 commit 1b2306eeb7.

* 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 commit ee76231974.

* Revert "Try fix spellcheck"

This reverts commit dc8f04afb9.

* 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>
This commit is contained in:
Stefan Markovic 2023-10-20 16:28:07 +02:00 коммит произвёл GitHub
Родитель 1f936df3eb
Коммит 812b343776
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
109 изменённых файлов: 4978 добавлений и 37 удалений

5
.github/actions/spell-check/expect.txt поставляемый
Просмотреть файл

@ -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}

Двоичные данные
doc/images/icons/Environment Manager.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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);
}
}
}

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 460 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 432 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 432 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 637 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 283 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 456 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 432 B

Двоичный файл не отображается.

После

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

Просмотреть файл

@ -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;
}
}

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 94 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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=&#xF133;}"
Icon="{ui:FontIcon Glyph=&#xF133;}"
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;

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше