mirror of
https://github.com/microsoft/PowerToys
synced 2024-11-21 15:53:19 +00:00
Hosts file editor (#20462)
This commit is contained in:
parent
ab41b61e84
commit
b2e1337d4e
@ -65,6 +65,10 @@
|
||||
"modules\\FileExplorerPreview\\PowerToys.SvgThumbnailProvider.dll",
|
||||
"modules\\FileExplorerPreview\\PowerToys.SvgThumbnailProvider.comhost.dll",
|
||||
|
||||
"modules\\Hosts\\PowerToys.HostsModuleInterface.dll",
|
||||
"modules\\Hosts\\PowerToys.Hosts.dll",
|
||||
"modules\\Hosts\\PowerToys.Hosts.exe",
|
||||
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizer.exe",
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizer.dll",
|
||||
"modules\\ImageResizer\\PowerToys.ImageResizerExt.dll",
|
||||
@ -217,6 +221,7 @@
|
||||
"modules\\FileExplorerPreview\\Microsoft.Web.WebView2.WinForms.dll",
|
||||
"modules\\FileExplorerPreview\\Microsoft.Web.WebView2.Wpf.dll",
|
||||
"modules\\FileExplorerPreview\\WebView2Loader.dll",
|
||||
"modules\\Hosts\\Microsoft.Graphics.Canvas.Interop.dll",
|
||||
"modules\\launcher\\e_sqlite3.dll",
|
||||
"modules\\launcher\\LazyCache.dll",
|
||||
"modules\\launcher\\SQLitePCLRaw.batteries_v2.dll",
|
||||
|
@ -200,6 +200,7 @@ steps:
|
||||
**\PreviewPaneUnitTests.dll
|
||||
**\UnitTests-SvgThumbnailProvider.dll
|
||||
**\UnitTests-SvgPreviewHandler.dll
|
||||
**\Hosts.Tests.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
|
||||
|
@ -449,6 +449,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MeasureToolUI", "src\module
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerAccentKeyboardService", "src\modules\poweraccent\PowerAccentKeyboardService\PowerAccentKeyboardService.vcxproj", "{C97D9A5D-206C-454E-997E-009E227D7F02}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts", "src\modules\Hosts\Hosts\Hosts.csproj", "{31D1C81D-765F-4446-AA62-E743F6325049}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts.Tests", "src\modules\Hosts\Hosts.Tests\Hosts.Tests.csproj", "{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HostsModuleInterface", "src\modules\Hosts\HostsModuleInterface\HostsModuleInterface.vcxproj", "{B41B888C-7DB8-4747-B262-4062E05A230D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@ -1804,6 +1812,42 @@ Global
|
||||
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x64.Build.0 = Release|x64
|
||||
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.ActiveCfg = Release|x64
|
||||
{C97D9A5D-206C-454E-997E-009E227D7F02}.Release|x86.Build.0 = Release|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x64.Build.0 = Debug|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Debug|x86.Build.0 = Debug|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|x64.ActiveCfg = Release|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|x64.Build.0 = Release|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|x86.ActiveCfg = Release|x64
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049}.Release|x86.Build.0 = Release|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x64.Build.0 = Debug|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Debug|x86.Build.0 = Debug|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.ActiveCfg = Release|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x64.Build.0 = Release|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x86.ActiveCfg = Release|x64
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A}.Release|x86.Build.0 = Release|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x64.Build.0 = Debug|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Debug|x86.Build.0 = Debug|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.ActiveCfg = Release|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.Build.0 = Release|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x86.ActiveCfg = Release|x64
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x86.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -1956,6 +2000,10 @@ Global
|
||||
{92C39820-9F84-4529-BC7D-22AAE514D63B} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{515554D1-D004-4F7F-A107-2211FC0F6B2C} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{C97D9A5D-206C-454E-997E-009E227D7F02} = {0F14491C-6369-4C45-AAA8-135814E66E6B}
|
||||
{31D1C81D-765F-4446-AA62-E743F6325049} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
{F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{E2D03E0F-7A75-4813-9F4B-D8763D43FD3A} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
{B41B888C-7DB8-4747-B262-4062E05A230D} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
@ -15,6 +15,7 @@
|
||||
<?define MouseUtilsProjectName="MouseUtils"?>
|
||||
<?define AlwaysOnTopProjectName="AlwaysOnTop"?>
|
||||
<?define MeasureToolProjectName="MeasureTool"?>
|
||||
<?define HostsProjectName="Hosts"?>
|
||||
|
||||
<?define RepoDir="$(var.ProjectDir)..\..\" ?>
|
||||
<?if $(var.Platform) = x64?>
|
||||
@ -121,13 +122,17 @@
|
||||
|
||||
<?define MeasureToolFiles=Ijwhost.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.MeasureToolCore.dll;PowerToys.MeasureToolUI.deps.json;PowerToys.MeasureToolUI.dll;PowerToys.MeasureToolUI.exe;PowerToys.MeasureToolUI.runtimeconfig.json;resources.pri;System.CodeDom.dll;System.Management.dll;WinRT.Runtime.dll;WinUIEx.dll?>
|
||||
|
||||
<?define PowerRenameMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||
<?define HostsFiles=ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.dll;CommunityToolkit.Mvvm.dll;CommunityToolkit.WinUI.dll;CommunityToolkit.WinUI.UI.Controls.Core.dll;CommunityToolkit.WinUI.UI.Controls.DataGrid.dll;CommunityToolkit.WinUI.UI.Controls.Input.dll;CommunityToolkit.WinUI.UI.Controls.Layout.dll;CommunityToolkit.WinUI.UI.Controls.Markdown.dll;CommunityToolkit.WinUI.UI.Controls.Media.dll;CommunityToolkit.WinUI.UI.Controls.Primitives.dll;CommunityToolkit.WinUI.UI.dll;ControlzEx.dll;Ijwhost.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.Configuration.CommandLine.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.Configuration.EnvironmentVariables.dll;Microsoft.Extensions.Configuration.FileExtensions.dll;Microsoft.Extensions.Configuration.Json.dll;Microsoft.Extensions.Configuration.UserSecrets.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.DependencyInjection.dll;Microsoft.Extensions.FileProviders.Abstractions.dll;Microsoft.Extensions.FileProviders.Physical.dll;Microsoft.Extensions.FileSystemGlobbing.dll;Microsoft.Extensions.Hosting.Abstractions.dll;Microsoft.Extensions.Hosting.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Logging.Configuration.dll;Microsoft.Extensions.Logging.Console.dll;Microsoft.Extensions.Logging.Debug.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Logging.EventLog.dll;Microsoft.Extensions.Logging.EventSource.dll;Microsoft.Extensions.Options.ConfigurationExtensions.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;Microsoft.Xaml.Behaviors.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;PowerToys.Common.UI.dll;PowerToys.Hosts.deps.json;PowerToys.Hosts.dll;PowerToys.Hosts.exe;PowerToys.Hosts.runtimeconfig.json;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.Settings.UI.Lib.dll;resources.pri;System.IO.Abstractions.dll;System.Management.dll;System.Text.Json.dll;WinRT.Runtime.dll;WinUIEx.dll;icon.ico?>
|
||||
|
||||
<?define PowerRenameMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||
|
||||
<?define WinAppSDKFiles=CoreMessagingXP.dll;DWriteCore.dll;DwmSceneI.dll;MRM.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.ui.xaml.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;PushNotificationsLongRunningTask.ProxyStub.dll;WinUIEdit.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;dcompi.dll;dwmcorei.dll;marshal.dll;wuceffectsi.dll?>
|
||||
|
||||
<?define PowerToysInteropFiles=concrt140.dll;msvcp140.dll;msvcp140_1.dll;msvcp140_2.dll;msvcp140_atomic_wait.dll;msvcp140_codecvt_ids.dll;PowerToys.Interop.dll;vcamp140.dll;vccorlib140.dll;vcomp140.dll;vcruntime140.dll;vcruntime140_1.dll?>
|
||||
|
||||
<?define MeasureToolMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||
|
||||
<?define HostsMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||
|
||||
<?define PowerAccentFiles=ControlzEx.dll;GongSolutions.WPF.DragDrop.dll;Ijwhost.dll;MahApps.Metro.dll;Microsoft.Xaml.Behaviors.dll;PowerAccent.Core.dll;PowerAccent.deps.json;PowerAccent.dll;PowerAccent.exe;PowerAccent.runtimeconfig.json;PowerToys.PowerAccentModuleInterface.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.PowerAccent.deps.json;PowerToys.PowerAccent.dll;PowerToys.PowerAccent.exe;PowerToys.PowerAccent.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;System.IO.Abstractions.dll;System.Management.dll;System.Text.Json.dll;Vanara.Core.dll;Vanara.PInvoke.ComCtl32.dll;Vanara.PInvoke.Cryptography.dll;Vanara.PInvoke.Gdi32.dll;Vanara.PInvoke.Kernel32.dll;Vanara.PInvoke.Ole.dll;Vanara.PInvoke.Rpc.dll;Vanara.PInvoke.Security.dll;Vanara.PInvoke.Shared.dll;Vanara.PInvoke.Shell32.dll;Vanara.PInvoke.ShlwApi.dll;Vanara.PInvoke.User32.dll;PowerToys.PowerAccentKeyboardService.dll;Microsoft.Windows.SDK.NET.dll;WinRT.Runtime.dll?>
|
||||
|
||||
@ -543,7 +548,14 @@
|
||||
<Directory Id="MeasureToolMicrosoftUIXamlAssetsInstallFolder" Name="Assets" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
|
||||
<!-- Hosts -->
|
||||
<Directory Id="HostsInstallFolder" Name="$(var.HostsProjectName)">
|
||||
<Directory Id="HostsMicrosoftUIXamlInstallFolder" Name="Microsoft.UI.Xaml">
|
||||
<Directory Id="HostsMicrosoftUIXamlAssetsInstallFolder" Name="Assets" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<!-- Launcher -->
|
||||
<Directory Id="LauncherInstallFolder" Name="launcher">
|
||||
<Directory Id="LauncherImagesFolder" Name="Images" />
|
||||
@ -1079,6 +1091,28 @@
|
||||
<?endforeach?>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Hosts -->
|
||||
<DirectoryRef Id="HostsInstallFolder" FileSource="$(var.BinDir)modules\$(var.HostsProjectName)">
|
||||
<Component Id="Module_HostsInterface" Win64="yes">
|
||||
<File Source="$(var.BinDir)modules\$(var.HostsProjectName)\PowerToys.HostsModuleInterface.dll" />
|
||||
</Component>
|
||||
|
||||
<?foreach File in $(var.HostsFiles)?>
|
||||
<Component Id="H_$(var.File)" Win64="yes">
|
||||
<File Id="H_$(var.File)" Source="$(var.BinDir)modules\$(var.HostsProjectName)\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="HostsMicrosoftUIXamlAssetsInstallFolder" FileSource="$(var.BinDir)modules\$(var.HostsProjectName)\Microsoft.UI.Xaml\Assets">
|
||||
<?foreach File in $(var.HostsMicrosoftUIXamlAssetsInstallFiles)?>
|
||||
<Component Id="HostsMicrosoftUIXamlAssets_$(var.File)" Win64="yes">
|
||||
<File Id="HostsMicrosoftUIXamlAssetsFile_$(var.File)" Source="$(var.BinDir)modules\$(var.HostsProjectName)\Microsoft.UI.Xaml\Assets\$(var.File)" />
|
||||
</Component>
|
||||
<?endforeach?>
|
||||
</DirectoryRef>
|
||||
|
||||
|
||||
<!-- SettingsV2 components -->
|
||||
<DirectoryRef Id="SettingsV2InstallFolder" FileSource="$(var.BinDir)Settings\">
|
||||
<?foreach File in $(var.SettingsV2Files)?>
|
||||
@ -1227,6 +1261,13 @@
|
||||
<?foreach File in $(var.MeasureToolMicrosoftUIXamlAssetsInstallFiles)?>
|
||||
<ComponentRef Id="MeasureToolMicrosoftUIXamlAssets_$(var.File)" />
|
||||
<?endforeach?>
|
||||
<ComponentRef Id="Module_HostsInterface"/>
|
||||
<?foreach File in $(var.HostsFiles)?>
|
||||
<ComponentRef Id="H_$(var.File)" />
|
||||
<?endforeach?>
|
||||
<?foreach File in $(var.HostsMicrosoftUIXamlAssetsInstallFiles)?>
|
||||
<ComponentRef Id="HostsMicrosoftUIXamlAssets_$(var.File)" />
|
||||
<?endforeach?>
|
||||
|
||||
<?foreach File in $(var.SettingsV2Files)?>
|
||||
<ComponentRef Id="SV2C_$(var.File)" />
|
||||
@ -1580,7 +1621,7 @@
|
||||
<!-- Localization languages shipped with WinAppSDK. We should ship these as well. -->
|
||||
<?define WinAppSDKLocLanguageList = af-ZA;ar-SA;az-Latn-AZ;bg-BG;bs-Latn-BA;ca-ES;cs-CZ;cy-GB;da-DK;de-DE;el-GR;en-GB;en-us;es-ES;es-MX;et-EE;eu-ES;fa-IR;fi-FI;fr-CA;fr-FR;gl-ES;he-IL;hi-IN;hr-HR;hu-HU;id-ID;is-IS;it-IT;ja-JP;ka-GE;kk-KZ;ko-KR;lt-LT;lv-LV;ms-MY;nb-NO;nl-NL;nn-NO;pl-PL;pt-BR;pt-PT;ro-RO;ru-RU;sk-SK;sl-SI;sq-AL;sr-Cyrl-RS;sr-Latn-RS;sv-SE;th-TH;tr-TR;uk-UA;vi-VN;zh-CN;zh-TW?>
|
||||
<Fragment>
|
||||
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder?>
|
||||
<?foreach ParentDirectory in SettingsV2InstallFolder;PowerRenameInstallFolder;MeasureToolInstallFolder;HostsInstallFolder?>
|
||||
<DirectoryRef Id="$(var.ParentDirectory)">
|
||||
<?foreach Language in $(var.WinAppSDKLocLanguageList)?>
|
||||
<?if $(var.Language) = af-ZA?>
|
||||
@ -1899,6 +1940,13 @@
|
||||
<File Id="MeasureTool_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
|
||||
<File Id="MeasureTool_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.MeasureToolProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
|
||||
</Component>
|
||||
<Component
|
||||
Id="Hosts_WinAppSDKLoc_$(var.IdSafeLanguage)_Component"
|
||||
Directory="WinAppSDKLoc$(var.IdSafeLanguage)HostsInstallFolder"
|
||||
Guid="$(var.CompGUIDPrefix)04">
|
||||
<File Id="Hosts_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlMui_File" Source="$(var.BinDir)modules\$(var.HostsProjectName)\$(var.Language)\Microsoft.ui.xaml.dll.mui" />
|
||||
<File Id="Hosts_WinAppSDKLoc_$(var.IdSafeLanguage)_XamlPhoneMui_File" Source="$(var.BinDir)modules\$(var.HostsProjectName)\$(var.Language)\Microsoft.UI.Xaml.Phone.dll.mui" />
|
||||
</Component>
|
||||
<?undef IdSafeLanguage?>
|
||||
<?undef CompGUIDPrefix?>
|
||||
<?endforeach?>
|
||||
|
@ -1049,7 +1049,7 @@ UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
std::wstring installationFolder, winAppSDKFilesSrcDir, settingsDir, powerRenameDir, measureToolDir;
|
||||
std::wstring installationFolder, winAppSDKFilesSrcDir, settingsDir, powerRenameDir, measureToolDir, hostsFileEditorDir;
|
||||
|
||||
hr = WcaInitialize(hInstall, "CreateWinAppSDKHardlinksCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
@ -1058,6 +1058,7 @@ UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
ExitOnFailure(hr, "Failed to get installation folder");
|
||||
|
||||
winAppSDKFilesSrcDir = installationFolder + L"dll\\WinAppSDK\\";
|
||||
hostsFileEditorDir = installationFolder + L"modules\\Hosts\\";
|
||||
settingsDir = installationFolder + L"Settings\\";
|
||||
powerRenameDir = installationFolder + L"modules\\PowerRename\\";
|
||||
measureToolDir = installationFolder + L"modules\\MeasureTool\\";
|
||||
@ -1065,6 +1066,7 @@ UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
for (auto file : winAppSdkFiles)
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (hostsFileEditorDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (settingsDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (powerRenameDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (measureToolDir + file).c_str(), ec);
|
||||
@ -1088,7 +1090,7 @@ UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
std::wstring installationFolder, interopFilesSrcDir, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir,
|
||||
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir;
|
||||
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, hostsFileEditorDir;
|
||||
|
||||
hr = WcaInitialize(hInstall, "CreatePTInteropHardlinksCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
@ -1101,6 +1103,7 @@ UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
powerOCRDir = installationFolder + L"modules\\PowerOCR\\";
|
||||
launcherDir = installationFolder + L"modules\\launcher\\";
|
||||
fancyZonesDir = installationFolder + L"modules\\FancyZones\\";
|
||||
hostsFileEditorDir = installationFolder + L"modules\\Hosts\\";
|
||||
imageResizerDir = installationFolder + L"modules\\ImageResizer\\";
|
||||
settingsDir = installationFolder + L"Settings\\";
|
||||
awakeDir = installationFolder + L"modules\\Awake\\";
|
||||
@ -1114,6 +1117,7 @@ UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (powerOCRDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (launcherDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (fancyZonesDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (hostsFileEditorDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (imageResizerDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (settingsDir + file).c_str(), ec);
|
||||
std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (awakeDir + file).c_str(), ec);
|
||||
@ -1138,7 +1142,7 @@ UINT __stdcall DeleteWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
std::wstring installationFolder, settingsDir, powerRenameDir, measureToolDir;
|
||||
std::wstring installationFolder, settingsDir, powerRenameDir, measureToolDir, hostsFileEditorDir;
|
||||
|
||||
hr = WcaInitialize(hInstall, "DeleteWinAppSDKHardlinksCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
@ -1146,6 +1150,7 @@ UINT __stdcall DeleteWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
hr = getInstallFolder(hInstall, installationFolder);
|
||||
ExitOnFailure(hr, "Failed to get installation folder");
|
||||
|
||||
hostsFileEditorDir = installationFolder + L"modules\\Hosts\\";
|
||||
settingsDir = installationFolder + L"Settings\\";
|
||||
powerRenameDir = installationFolder + L"modules\\PowerRename\\";
|
||||
measureToolDir = installationFolder + L"modules\\MeasureTool\\";
|
||||
@ -1154,6 +1159,7 @@ UINT __stdcall DeleteWinAppSDKHardlinksCA(MSIHANDLE hInstall)
|
||||
{
|
||||
for (auto file : winAppSdkFiles)
|
||||
{
|
||||
DeleteFile((hostsFileEditorDir + file).c_str());
|
||||
DeleteFile((settingsDir + file).c_str());
|
||||
DeleteFile((powerRenameDir + file).c_str());
|
||||
DeleteFile((measureToolDir + file).c_str());
|
||||
@ -1178,7 +1184,7 @@ UINT __stdcall DeletePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
HRESULT hr = S_OK;
|
||||
UINT er = ERROR_SUCCESS;
|
||||
std::wstring installationFolder, interopFilesSrcDir, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir,
|
||||
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir;
|
||||
imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, hostsFileEditorDir;
|
||||
|
||||
hr = WcaInitialize(hInstall, "DeletePTInteropHardlinksCA");
|
||||
ExitOnFailure(hr, "Failed to initialize");
|
||||
@ -1190,6 +1196,7 @@ UINT __stdcall DeletePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
powerOCRDir = installationFolder + L"modules\\PowerOCR\\";
|
||||
launcherDir = installationFolder + L"modules\\launcher\\";
|
||||
fancyZonesDir = installationFolder + L"modules\\FancyZones\\";
|
||||
hostsFileEditorDir = installationFolder + L"modules\\Hosts\\";
|
||||
imageResizerDir = installationFolder + L"modules\\ImageResizer\\";
|
||||
settingsDir = installationFolder + L"Settings\\";
|
||||
awakeDir = installationFolder + L"modules\\Awake\\";
|
||||
@ -1204,6 +1211,7 @@ UINT __stdcall DeletePTInteropHardlinksCA(MSIHANDLE hInstall)
|
||||
DeleteFile((powerOCRDir + file).c_str());
|
||||
DeleteFile((launcherDir + file).c_str());
|
||||
DeleteFile((fancyZonesDir + file).c_str());
|
||||
DeleteFile((hostsFileEditorDir + file).c_str());
|
||||
DeleteFile((imageResizerDir + file).c_str());
|
||||
DeleteFile((settingsDir + file).c_str());
|
||||
DeleteFile((awakeDir + file).c_str());
|
||||
|
@ -23,6 +23,7 @@ namespace Common.UI
|
||||
FileExplorer,
|
||||
ShortcutGuide,
|
||||
VideoConference,
|
||||
Hosts,
|
||||
}
|
||||
|
||||
private static string SettingsWindowNameToString(SettingsWindow value)
|
||||
@ -53,6 +54,8 @@ namespace Common.UI
|
||||
return "ShortcutGuide";
|
||||
case SettingsWindow.VideoConference:
|
||||
return "VideoConference";
|
||||
case SettingsWindow.Hosts:
|
||||
return "Hosts";
|
||||
default:
|
||||
{
|
||||
return string.Empty;
|
||||
|
@ -35,6 +35,8 @@ struct LogSettings
|
||||
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
|
||||
inline const static std::string powerOcrLoggerName = "TextExtractor";
|
||||
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";
|
||||
inline const static std::string hostsLoggerName = "hosts";
|
||||
inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.txt";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
LogSettings();
|
||||
|
83
src/modules/Hosts/Hosts.Tests/EntryTest.cs
Normal file
83
src/modules/Hosts/Hosts.Tests/EntryTest.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// 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 Hosts.Models;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Hosts.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class EntryTest
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow("\t\t10.1.1.1\t\thost\t\t", "10.1.1.1", "host", "", true)]
|
||||
[DataRow(" 10.1.1.1 host ", "10.1.1.1", "host", "", true)]
|
||||
[DataRow("10.1.1.1 host", "10.1.1.1", "host", "", true)]
|
||||
[DataRow("\t\t#\t\t10.1.1.1\thost\t\t", "10.1.1.1", "host", "", false)]
|
||||
[DataRow(" # 10.1.1.1 host ", "10.1.1.1", "host", "", false)]
|
||||
[DataRow("#10.1.1.1 host", "10.1.1.1", "host", "", false)]
|
||||
[DataRow("\t\t10.1.1.1\t\thost\t\t#\t\tcomment\t\t", "10.1.1.1", "host", "comment", true)]
|
||||
[DataRow(" 10.1.1.1 host # comment ", "10.1.1.1", "host", "comment", true)]
|
||||
[DataRow("10.1.1.1 host#comment", "10.1.1.1", "host", "comment", true)]
|
||||
[DataRow("\t\t#\t\t10.1.1.1\thost\t\t#\t\tcomment\t\t", "10.1.1.1", "host", "comment", false)]
|
||||
[DataRow(" # 10.1.1.1 host # comment ", "10.1.1.1", "host", "comment", false)]
|
||||
[DataRow("#10.1.1.1 host#comment", "10.1.1.1", "host", "comment", false)]
|
||||
[DataRow("# #10.1.1.1 host#comment", "10.1.1.1", "host", "comment", false)]
|
||||
[DataRow("# #\t10.1.1.1 host#comment", "10.1.1.1", "host", "comment", false)]
|
||||
[DataRow("# # \t10.1.1.1 host#comment", "10.1.1.1", "host", "comment", false)]
|
||||
public void Valid_Entry_SingleHost(string line, string address, string host, string comment, bool active)
|
||||
{
|
||||
var entry = new Entry(line);
|
||||
|
||||
Assert.AreEqual(entry.Address, address);
|
||||
Assert.AreEqual(entry.Hosts, host);
|
||||
Assert.AreEqual(entry.Comment, comment);
|
||||
Assert.AreEqual(entry.Active, active);
|
||||
Assert.IsTrue(entry.Valid);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("\t\t10.1.1.1\t\thost host.local\t\t", "10.1.1.1", "host host.local", "", true)]
|
||||
[DataRow(" 10.1.1.1 host host.local ", "10.1.1.1", "host host.local", "", true)]
|
||||
[DataRow("10.1.1.1 host host.local", "10.1.1.1", "host host.local", "", true)]
|
||||
[DataRow("\t\t#\t\t10.1.1.1\thost\t\thost.local\t\t", "10.1.1.1", "host host.local", "", false)]
|
||||
[DataRow(" # 10.1.1.1 host host.local ", "10.1.1.1", "host host.local", "", false)]
|
||||
[DataRow("#10.1.1.1 host host.local", "10.1.1.1", "host host.local", "", false)]
|
||||
[DataRow("\t\t10.1.1.1\t\thost\t\thost.local\t\t#\t\tcomment\t\t", "10.1.1.1", "host host.local", "comment", true)]
|
||||
[DataRow(" 10.1.1.1 host host.local # comment ", "10.1.1.1", "host host.local", "comment", true)]
|
||||
[DataRow("10.1.1.1 host host.local#comment", "10.1.1.1", "host host.local", "comment", true)]
|
||||
[DataRow("\t\t#\t\t10.1.1.1\thost\t\thost.local\t\t#\t\tcomment\t\t", "10.1.1.1", "host host.local", "comment", false)]
|
||||
[DataRow(" # 10.1.1.1 host host.local # comment ", "10.1.1.1", "host host.local", "comment", false)]
|
||||
[DataRow("#10.1.1.1 host host.local#comment", "10.1.1.1", "host host.local", "comment", false)]
|
||||
public void Valid_Entry_MultipleHosts(string line, string address, string host, string comment, bool active)
|
||||
{
|
||||
var entry = new Entry(line);
|
||||
|
||||
Assert.AreEqual(entry.Address, address);
|
||||
Assert.AreEqual(entry.Hosts, host);
|
||||
Assert.AreEqual(entry.Comment, comment);
|
||||
Assert.AreEqual(entry.Active, active);
|
||||
Assert.IsTrue(entry.Valid);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("\t\t10.1.1.1\t\t")]
|
||||
[DataRow(" 10.1.1.1 ")]
|
||||
[DataRow("10.1.1.1")]
|
||||
[DataRow("\t\thost\t\t")]
|
||||
[DataRow(" host ")]
|
||||
[DataRow("host")]
|
||||
[DataRow("\t\t10\t\thost")]
|
||||
[DataRow(" 10 host ")]
|
||||
[DataRow("10 host")]
|
||||
[DataRow("\t\thost\t\t10.1.1.1")]
|
||||
[DataRow(" host 10.1.1.1")]
|
||||
[DataRow("host 10.1.1.1")]
|
||||
public void Not_Valid_Entry(string line)
|
||||
{
|
||||
var entry = new Entry(line);
|
||||
Assert.IsFalse(entry.Valid);
|
||||
}
|
||||
}
|
||||
}
|
30
src/modules/Hosts/Hosts.Tests/Hosts.Tests.csproj
Normal file
30
src/modules/Hosts/Hosts.Tests/Hosts.Tests.csproj
Normal file
@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\Hosts\Hosts.Tests\</OutputPath>
|
||||
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
|
||||
<RootNamespace>Hosts.Tests</RootNamespace>
|
||||
<AssemblyName>PowerToys.Hosts.Tests</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="12.2.5" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="12.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Hosts\Hosts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
255
src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs
Normal file
255
src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs
Normal file
@ -0,0 +1,255 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Hosts.Helpers;
|
||||
using Hosts.Models;
|
||||
using Hosts.Settings;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Hosts.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class HostsServiceTest
|
||||
{
|
||||
private static Mock<IElevationHelper> _elevationHelper;
|
||||
|
||||
[ClassInitialize]
|
||||
public static void ClassInitialize(TestContext context)
|
||||
{
|
||||
_elevationHelper = new Mock<IElevationHelper>();
|
||||
_elevationHelper.Setup(m => m.IsElevated).Returns(true);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Hosts_Exists()
|
||||
{
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
|
||||
var result = service.Exists();
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Hosts_Not_Exists()
|
||||
{
|
||||
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
|
||||
{
|
||||
})
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
var result = service.Exists();
|
||||
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Host_Added()
|
||||
{
|
||||
var content =
|
||||
@"10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@" 10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
# 10.1.1.30 host30 host30.local # new entry
|
||||
";
|
||||
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
entries.Add(new Entry("10.1.1.30", "host30 host30.local", "new entry", false));
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Host_Deleted()
|
||||
{
|
||||
var content =
|
||||
@"10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@"10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
entries.RemoveAt(0);
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Host_Updated()
|
||||
{
|
||||
var content =
|
||||
@"10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@"# 10.1.1.10 host host.local host1.local # updated comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
var entry = entries[0];
|
||||
entry.Address = "10.1.1.10";
|
||||
entry.Hosts = "host host.local host1.local";
|
||||
entry.Comment = "updated comment";
|
||||
entry.Active = false;
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Empty_Hosts()
|
||||
{
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
|
||||
|
||||
await service.WriteAsync(string.Empty, Enumerable.Empty<Entry>());
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AdditionalLines_Top()
|
||||
{
|
||||
var content =
|
||||
@"# header
|
||||
10.1.1.1 host host.local # comment
|
||||
# comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
# footer
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@"# header
|
||||
# comment
|
||||
# footer
|
||||
10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
";
|
||||
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(AdditionalLinesPosition.Top);
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (additionalLines, entries) = await service.ReadAsync();
|
||||
await service.WriteAsync(additionalLines, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task AdditionalLines_Bottom()
|
||||
{
|
||||
var content =
|
||||
@"# header
|
||||
10.1.1.1 host host.local # comment
|
||||
# comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
# footer
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@"10.1.1.1 host host.local # comment
|
||||
10.1.1.2 host2 host2.local # another comment
|
||||
# header
|
||||
# comment
|
||||
# footer
|
||||
";
|
||||
|
||||
var fileSystem = new MockFileSystem
|
||||
{
|
||||
FileSystemWatcher = new TestFileSystemWatcherFactory(),
|
||||
};
|
||||
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(AdditionalLinesPosition.Bottom);
|
||||
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (additionalLines, entries) = await service.ReadAsync();
|
||||
await service.WriteAsync(additionalLines, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
}
|
||||
}
|
49
src/modules/Hosts/Hosts.Tests/TestFileSystemWatcher.cs
Normal file
49
src/modules/Hosts/Hosts.Tests/TestFileSystemWatcher.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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.ComponentModel;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Hosts.Tests
|
||||
{
|
||||
public class TestFileSystemWatcher : FileSystemWatcherBase
|
||||
{
|
||||
public override bool IncludeSubdirectories { get; set; }
|
||||
|
||||
public override bool EnableRaisingEvents { get; set; }
|
||||
|
||||
public override string Filter { get; set; }
|
||||
|
||||
public override int InternalBufferSize { get; set; }
|
||||
|
||||
public override NotifyFilters NotifyFilter { get; set; }
|
||||
|
||||
public override string Path { get; set; }
|
||||
|
||||
public override ISite Site { get; set; }
|
||||
|
||||
public override ISynchronizeInvoke SynchronizingObject { get; set; }
|
||||
|
||||
public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) => default(WaitForChangedResult);
|
||||
|
||||
public override WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) => default(WaitForChangedResult);
|
||||
|
||||
public TestFileSystemWatcher(string path) => Path = path;
|
||||
|
||||
public TestFileSystemWatcher(string path, string filter)
|
||||
{
|
||||
Path = path;
|
||||
Filter = filter;
|
||||
}
|
||||
|
||||
public override void BeginInit()
|
||||
{
|
||||
}
|
||||
|
||||
public override void EndInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 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.IO.Abstractions;
|
||||
|
||||
namespace Hosts.Tests
|
||||
{
|
||||
public class TestFileSystemWatcherFactory : IFileSystemWatcherFactory
|
||||
{
|
||||
public IFileSystemWatcher CreateNew() => new TestFileSystemWatcher(null);
|
||||
|
||||
public IFileSystemWatcher CreateNew(string path) => new TestFileSystemWatcher(path);
|
||||
|
||||
public IFileSystemWatcher CreateNew(string path, string filter) => new TestFileSystemWatcher(path, filter);
|
||||
|
||||
public IFileSystemWatcher FromPath(string path) => new TestFileSystemWatcher(path);
|
||||
}
|
||||
}
|
115
src/modules/Hosts/Hosts/App.xaml
Normal file
115
src/modules/Hosts/Hosts/App.xaml
Normal file
@ -0,0 +1,115 @@
|
||||
<Application
|
||||
x:Class="Hosts.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Hosts">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<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>
|
113
src/modules/Hosts/Hosts/App.xaml.cs
Normal file
113
src/modules/Hosts/Hosts/App.xaml.cs
Normal file
@ -0,0 +1,113 @@
|
||||
// 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 System.Threading;
|
||||
using Hosts.Helpers;
|
||||
using Hosts.Settings;
|
||||
using Hosts.ViewModels;
|
||||
using Hosts.Views;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Hosts
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private Window _window;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Host = Microsoft.Extensions.Hosting.Host.
|
||||
CreateDefaultBuilder().
|
||||
UseContentRoot(AppContext.BaseDirectory).
|
||||
ConfigureServices((context, services) =>
|
||||
{
|
||||
// Core Services
|
||||
services.AddSingleton<IFileSystem, FileSystem>();
|
||||
services.AddSingleton<IHostsService, HostsService>();
|
||||
services.AddSingleton<IUserSettings, UserSettings>();
|
||||
services.AddSingleton<IElevationHelper, ElevationHelper>();
|
||||
|
||||
// Views and ViewModels
|
||||
services.AddTransient<MainPage>();
|
||||
services.AddTransient<MainViewModel>();
|
||||
}).
|
||||
Build();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
// Delete old backups only if running elevated
|
||||
if (!GetService<IElevationHelper>().IsElevated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GetService<IHostsService>().CleanupBackup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to delete backup", ex);
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
{
|
||||
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
|
||||
{
|
||||
Logger.LogInfo($"Hosts started from the PowerToys Runner. Runner pid={powerToysRunnerPid}");
|
||||
|
||||
var dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting Hosts");
|
||||
dispatcher.TryEnqueue(App.Current.Exit);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo($"Hosts started detached from PowerToys Runner.");
|
||||
}
|
||||
|
||||
_window = new MainWindow();
|
||||
_window.Activate();
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
}
|
||||
}
|
20
src/modules/Hosts/Hosts/Helpers/ElevationHelper.cs
Normal file
20
src/modules/Hosts/Hosts/Helpers/ElevationHelper.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Security.Principal;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public class ElevationHelper : IElevationHelper
|
||||
{
|
||||
private readonly bool _isElevated;
|
||||
|
||||
public bool IsElevated => _isElevated;
|
||||
|
||||
public ElevationHelper()
|
||||
{
|
||||
_isElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
}
|
||||
}
|
238
src/modules/Hosts/Hosts/Helpers/HostsService.cs
Normal file
238
src/modules/Hosts/Hosts/Helpers/HostsService.cs
Normal file
@ -0,0 +1,238 @@
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Hosts.Models;
|
||||
using Hosts.Settings;
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public class HostsService : IHostsService, IDisposable
|
||||
{
|
||||
private const string _backupSuffix = $"_PowerToysBackup_";
|
||||
|
||||
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private readonly IElevationHelper _elevationHelper;
|
||||
private readonly IFileSystemWatcher _fileSystemWatcher;
|
||||
private readonly string _hostsFilePath;
|
||||
private bool _backupDone;
|
||||
private bool _disposed;
|
||||
|
||||
public string HostsFilePath => _hostsFilePath;
|
||||
|
||||
public event EventHandler FileChanged;
|
||||
|
||||
public HostsService(
|
||||
IFileSystem fileSystem,
|
||||
IUserSettings userSettings,
|
||||
IElevationHelper elevationHelper)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_userSettings = userSettings;
|
||||
_elevationHelper = elevationHelper;
|
||||
|
||||
_hostsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"System32\drivers\etc\hosts");
|
||||
|
||||
_fileSystemWatcher = _fileSystem.FileSystemWatcher.CreateNew();
|
||||
_fileSystemWatcher.Path = _fileSystem.Path.GetDirectoryName(HostsFilePath);
|
||||
_fileSystemWatcher.Filter = _fileSystem.Path.GetFileName(HostsFilePath);
|
||||
_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
_fileSystemWatcher.Changed += FileSystemWatcher_Changed;
|
||||
_fileSystemWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
return _fileSystem.File.Exists(HostsFilePath);
|
||||
}
|
||||
|
||||
public async Task<(string Unparsed, List<Entry> Entries)> ReadAsync()
|
||||
{
|
||||
var entries = new List<Entry>();
|
||||
var unparsedBuilder = new StringBuilder();
|
||||
|
||||
if (!Exists())
|
||||
{
|
||||
return (unparsedBuilder.ToString(), entries);
|
||||
}
|
||||
|
||||
var lines = await _fileSystem.File.ReadAllLinesAsync(HostsFilePath);
|
||||
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var entry = new Entry(line);
|
||||
|
||||
if (entry.Valid)
|
||||
{
|
||||
entries.Add(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unparsedBuilder.Length > 0)
|
||||
{
|
||||
unparsedBuilder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
unparsedBuilder.Append(line);
|
||||
}
|
||||
}
|
||||
|
||||
return (unparsedBuilder.ToString(), entries);
|
||||
}
|
||||
|
||||
public async Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries)
|
||||
{
|
||||
if (!_elevationHelper.IsElevated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lines = new List<string>();
|
||||
|
||||
if (entries.Any())
|
||||
{
|
||||
var addressPadding = entries.Max(e => e.Address.Length) + 1;
|
||||
var hostsPadding = entries.Max(e => e.Hosts.Length) + 1;
|
||||
var anyDisabled = entries.Any(e => !e.Active);
|
||||
|
||||
foreach (var e in entries)
|
||||
{
|
||||
var lineBuilder = new StringBuilder();
|
||||
|
||||
if (!e.Valid)
|
||||
{
|
||||
lineBuilder.Append(e.GetLine());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!e.Active)
|
||||
{
|
||||
lineBuilder.Append('#').Append(' ');
|
||||
}
|
||||
else if (anyDisabled)
|
||||
{
|
||||
lineBuilder.Append(' ').Append(' ');
|
||||
}
|
||||
|
||||
lineBuilder.Append(e.Address.PadRight(addressPadding));
|
||||
lineBuilder.Append(string.Join(' ', e.Hosts).PadRight(hostsPadding));
|
||||
|
||||
if (e.Comment != string.Empty)
|
||||
{
|
||||
lineBuilder.Append('#').Append(' ');
|
||||
lineBuilder.Append(e.Comment);
|
||||
}
|
||||
|
||||
lines.Add(lineBuilder.ToString().TrimEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(additionalLines))
|
||||
{
|
||||
if (_userSettings.AdditionalLinesPosition == AdditionalLinesPosition.Top)
|
||||
{
|
||||
lines.Insert(0, additionalLines);
|
||||
}
|
||||
else if (_userSettings.AdditionalLinesPosition == AdditionalLinesPosition.Bottom)
|
||||
{
|
||||
lines.Add(additionalLines);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _asyncLock.WaitAsync();
|
||||
_fileSystemWatcher.EnableRaisingEvents = false;
|
||||
|
||||
if (!_backupDone && Exists())
|
||||
{
|
||||
_fileSystem.File.Copy(HostsFilePath, HostsFilePath + _backupSuffix + DateTime.Now.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture));
|
||||
_backupDone = true;
|
||||
}
|
||||
|
||||
await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to write hosts file", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_fileSystemWatcher.EnableRaisingEvents = true;
|
||||
_asyncLock.Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> PingAsync(string address)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ping = new Ping();
|
||||
var reply = await ping.SendPingAsync(address, 4000); // 4000 is the default ping timeout for ping.exe
|
||||
return reply.Status == IPStatus.Success;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanupBackup()
|
||||
{
|
||||
Directory.GetFiles(Path.GetDirectoryName(HostsFilePath), $"*{_backupSuffix}*")
|
||||
.Select(f => new FileInfo(f))
|
||||
.Where(f => f.CreationTime < DateTime.Now.AddDays(-15))
|
||||
.ToList()
|
||||
.ForEach(f => f.Delete());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
_fileSystemWatcher.EnableRaisingEvents = false;
|
||||
FileChanged?.Invoke(this, EventArgs.Empty);
|
||||
_fileSystemWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_asyncLock.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/modules/Hosts/Hosts/Helpers/IElevationHelper.cs
Normal file
11
src/modules/Hosts/Hosts/Helpers/IElevationHelper.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public interface IElevationHelper
|
||||
{
|
||||
bool IsElevated { get; }
|
||||
}
|
||||
}
|
26
src/modules/Hosts/Hosts/Helpers/IHostsService.cs
Normal file
26
src/modules/Hosts/Hosts/Helpers/IHostsService.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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 Hosts.Models;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public interface IHostsService : IDisposable
|
||||
{
|
||||
string HostsFilePath { get; }
|
||||
|
||||
event EventHandler FileChanged;
|
||||
|
||||
Task<(string Unparsed, List<Entry> Entries)> ReadAsync();
|
||||
|
||||
Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries);
|
||||
|
||||
Task<bool> PingAsync(string address);
|
||||
|
||||
void CleanupBackup();
|
||||
}
|
||||
}
|
80
src/modules/Hosts/Hosts/Helpers/Logger.cs
Normal file
80
src/modules/Hosts/Hosts/Helpers/Logger.cs
Normal file
@ -0,0 +1,80 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using interop;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
// TODO: use centralized logger https://github.com/microsoft/PowerToys/issues/19650
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly IFileSystem _fileSystem = new FileSystem();
|
||||
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "Hosts\\Logs");
|
||||
|
||||
static Logger()
|
||||
{
|
||||
if (!_fileSystem.Directory.Exists(ApplicationLogPath))
|
||||
{
|
||||
_fileSystem.Directory.CreateDirectory(ApplicationLogPath);
|
||||
}
|
||||
|
||||
// Using InvariantCulture since this is used for a log file name
|
||||
var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, "ERROR");
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception ex)
|
||||
{
|
||||
Log(
|
||||
message + Environment.NewLine +
|
||||
ex?.Message + Environment.NewLine +
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
ex?.InnerException?.Message + Environment.NewLine +
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
ex?.StackTrace,
|
||||
"ERROR");
|
||||
}
|
||||
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
Log(message, "WARNING");
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, "INFO");
|
||||
}
|
||||
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||
Trace.Indent();
|
||||
Trace.WriteLine(GetCallerInfo());
|
||||
Trace.WriteLine(message);
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
StackTrace stackTrace = new StackTrace();
|
||||
|
||||
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||
var className = methodName?.DeclaringType.Name;
|
||||
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||
}
|
||||
}
|
||||
}
|
24
src/modules/Hosts/Hosts/Helpers/NativeMethods.cs
Normal file
24
src/modules/Hosts/Hosts/Helpers/NativeMethods.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern IntPtr SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
}
|
||||
}
|
14
src/modules/Hosts/Hosts/Helpers/StringHelper.cs
Normal file
14
src/modules/Hosts/Hosts/Helpers/StringHelper.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
public static string GetHostsWithComment(string hosts, string comment)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(comment) ? hosts : string.Concat(hosts, " - ", comment);
|
||||
}
|
||||
}
|
||||
}
|
60
src/modules/Hosts/Hosts/Helpers/ValidationHelper.cs
Normal file
60
src/modules/Hosts/Hosts/Helpers/ValidationHelper.cs
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
{
|
||||
public static class ValidationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the address is a valid IPv4
|
||||
/// </summary>
|
||||
public static bool ValidIPv4(string address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var regex = new Regex("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
|
||||
return regex.IsMatch(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the address is a valid IPv6
|
||||
/// </summary>
|
||||
public static bool ValidIPv6(string address)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var regex = new Regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$");
|
||||
return regex.IsMatch(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the hosts are valid
|
||||
/// </summary>
|
||||
public static bool ValidHosts(string hosts)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hosts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var host in hosts.Split(' '))
|
||||
{
|
||||
if (System.Uri.CheckHostName(host) == System.UriHostNameType.Unknown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
55
src/modules/Hosts/Hosts/Hosts.csproj
Normal file
55
src/modules/Hosts/Hosts/Hosts.csproj
Normal file
@ -0,0 +1,55 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>Hosts</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<RuntimeIdentifiers>win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\$(AssemblyName)</OutputPath>
|
||||
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)</IntermediateOutputPath>
|
||||
<RootNamespace>Hosts</RootNamespace>
|
||||
<AssemblyName>PowerToys.Hosts</AssemblyName>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.5" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22000.197" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.8" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="12.2.5" />
|
||||
<PackageReference Include="WinUIEx" Version="1.8.0" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MainPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
37
src/modules/Hosts/Hosts/MainWindow.xaml
Normal file
37
src/modules/Hosts/Hosts/MainWindow.xaml
Normal file
@ -0,0 +1,37 @@
|
||||
<winuiex:WindowEx
|
||||
x:Uid="Window"
|
||||
x:Class="Hosts.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:Hosts"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:Hosts.Views"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Width="680"
|
||||
MinWidth="480"
|
||||
MinHeight="320"
|
||||
mc:Ignorable="d">
|
||||
<winuiex:WindowEx.Backdrop>
|
||||
<winuiex:MicaSystemBackdrop />
|
||||
</winuiex:WindowEx.Backdrop>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="titleBar">
|
||||
<StackPanel
|
||||
Margin="16,8,8,8"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock FontFamily="{ThemeResource SymbolThemeFontFamily}" Text="" />
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="Hosts File Editor" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<views:MainPage Grid.Row="1" />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
71
src/modules/Hosts/Hosts/MainWindow.xaml.cs
Normal file
71
src/modules/Hosts/Hosts/MainWindow.xaml.cs
Normal file
@ -0,0 +1,71 @@
|
||||
// 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 Hosts.Helpers;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Hosts
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||
{
|
||||
SetTitleBar();
|
||||
}
|
||||
else
|
||||
{
|
||||
titleBar.Visibility = Visibility.Collapsed;
|
||||
|
||||
// Set window icon
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd);
|
||||
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.SetIcon("icon.ico");
|
||||
|
||||
if (ThemeHelpers.GetAppTheme() == AppTheme.Dark)
|
||||
{
|
||||
ThemeHelpers.SetImmersiveDarkMode(hWnd, true);
|
||||
}
|
||||
}
|
||||
|
||||
BringToForeground();
|
||||
}
|
||||
|
||||
private void SetTitleBar()
|
||||
{
|
||||
AppWindow window = this.GetAppWindow();
|
||||
window.TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
window.TitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
SetTitleBar(titleBar);
|
||||
}
|
||||
|
||||
private void BringToForeground()
|
||||
{
|
||||
var handle = this.GetWindowHandle();
|
||||
var fgHandle = NativeMethods.GetForegroundWindow();
|
||||
|
||||
var threadId1 = NativeMethods.GetWindowThreadProcessId(handle, System.IntPtr.Zero);
|
||||
var threadId2 = NativeMethods.GetWindowThreadProcessId(fgHandle, System.IntPtr.Zero);
|
||||
|
||||
if (threadId1 != threadId2)
|
||||
{
|
||||
NativeMethods.AttachThreadInput(threadId1, threadId2, true);
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
NativeMethods.AttachThreadInput(threadId1, threadId2, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
149
src/modules/Hosts/Hosts/Models/Entry.cs
Normal file
149
src/modules/Hosts/Hosts/Models/Entry.cs
Normal file
@ -0,0 +1,149 @@
|
||||
// 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.Net;
|
||||
using System.Text;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Hosts.Helpers;
|
||||
|
||||
namespace Hosts.Models
|
||||
{
|
||||
public partial class Entry : ObservableObject
|
||||
{
|
||||
private string _line;
|
||||
|
||||
private string _address;
|
||||
|
||||
public string Address
|
||||
{
|
||||
get => _address;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _address, value);
|
||||
OnPropertyChanged(nameof(Valid));
|
||||
}
|
||||
}
|
||||
|
||||
private string _hosts;
|
||||
|
||||
public string Hosts
|
||||
{
|
||||
get => _hosts;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _hosts, value);
|
||||
OnPropertyChanged(nameof(Valid));
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private string _comment;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _active;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool? _ping;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _pinging;
|
||||
|
||||
public bool Valid => ValidationHelper.ValidHosts(_hosts) && (ValidationHelper.ValidIPv4(_address) || ValidationHelper.ValidIPv6(_address));
|
||||
|
||||
public Entry()
|
||||
{
|
||||
}
|
||||
|
||||
public Entry(string line)
|
||||
{
|
||||
_line = line.Trim();
|
||||
Parse();
|
||||
}
|
||||
|
||||
public Entry(string address, string hosts, string comment, bool active)
|
||||
{
|
||||
Address = address.Trim();
|
||||
Hosts = hosts.Trim();
|
||||
Comment = comment.Trim();
|
||||
Active = active;
|
||||
}
|
||||
|
||||
public void Parse()
|
||||
{
|
||||
Active = !_line.StartsWith("#", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
var lineSplit = _line.TrimStart(' ', '#').Split('#');
|
||||
|
||||
if (lineSplit.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var addressHost = lineSplit[0];
|
||||
|
||||
var addressHostSplit = addressHost.Split(' ', '\t');
|
||||
var hostsBuilder = new StringBuilder();
|
||||
var commentBuilder = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < addressHostSplit.Length; i++)
|
||||
{
|
||||
var element = addressHostSplit[i].Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(element))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Address == null)
|
||||
{
|
||||
if (IPAddress.TryParse(element, out var _) && (element.Contains(':') || element.Contains('.')))
|
||||
{
|
||||
Address = element;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hostsBuilder.Length > 0)
|
||||
{
|
||||
hostsBuilder.Append(' ');
|
||||
}
|
||||
|
||||
hostsBuilder.Append(element);
|
||||
}
|
||||
}
|
||||
|
||||
Hosts = hostsBuilder.ToString();
|
||||
|
||||
for (var i = 1; i < lineSplit.Length; i++)
|
||||
{
|
||||
if (commentBuilder.Length > 0)
|
||||
{
|
||||
commentBuilder.Append('#');
|
||||
}
|
||||
|
||||
commentBuilder.Append(lineSplit[i]);
|
||||
}
|
||||
|
||||
Comment = commentBuilder.ToString().Trim();
|
||||
}
|
||||
|
||||
public Entry Clone()
|
||||
{
|
||||
return new Entry
|
||||
{
|
||||
_line = _line,
|
||||
Address = Address,
|
||||
Hosts = Hosts,
|
||||
Comment = Comment,
|
||||
Active = Active,
|
||||
};
|
||||
}
|
||||
|
||||
public string GetLine()
|
||||
{
|
||||
return _line;
|
||||
}
|
||||
}
|
||||
}
|
38
src/modules/Hosts/Hosts/Program.cs
Normal file
38
src/modules/Hosts/Hosts/Program.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// 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 Hosts.Helpers;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
|
||||
namespace Hosts
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
var instanceKey = AppInstance.FindOrRegisterForKey("PowerToys_Hosts_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 Hosts running. Exiting Hosts");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
15
src/modules/Hosts/Hosts/Settings/IUserSettings.cs
Normal file
15
src/modules/Hosts/Hosts/Settings/IUserSettings.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Hosts.Settings
|
||||
{
|
||||
public interface IUserSettings
|
||||
{
|
||||
public bool ShowStartupWarning { get; }
|
||||
|
||||
public AdditionalLinesPosition AdditionalLinesPosition { get; }
|
||||
}
|
||||
}
|
81
src/modules/Hosts/Hosts/Settings/UserSettings.cs
Normal file
81
src/modules/Hosts/Hosts/Settings/UserSettings.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 System.Threading;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Hosts.Settings
|
||||
{
|
||||
public class UserSettings : IUserSettings
|
||||
{
|
||||
private const string HostsModuleName = "Hosts";
|
||||
private const int MaxNumberOfRetry = 5;
|
||||
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
|
||||
public bool ShowStartupWarning { get; private set; }
|
||||
|
||||
public AdditionalLinesPosition AdditionalLinesPosition { get; private set; }
|
||||
|
||||
public UserSettings()
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
ShowStartupWarning = true;
|
||||
AdditionalLinesPosition = AdditionalLinesPosition.Top;
|
||||
|
||||
LoadSettingsFromJson();
|
||||
|
||||
_watcher = Helper.GetFileWatcher(HostsModuleName, "settings.json", () => LoadSettingsFromJson());
|
||||
}
|
||||
|
||||
private void LoadSettingsFromJson()
|
||||
{
|
||||
lock (_loadingSettingsLock)
|
||||
{
|
||||
var retry = true;
|
||||
var retryCount = 0;
|
||||
|
||||
while (retry)
|
||||
{
|
||||
try
|
||||
{
|
||||
retryCount++;
|
||||
|
||||
if (!_settingsUtils.SettingsExists(HostsModuleName))
|
||||
{
|
||||
Logger.LogInfo("Hosts settings.json was missing, creating a new one");
|
||||
var defaultSettings = new HostsSettings();
|
||||
defaultSettings.Save(_settingsUtils);
|
||||
}
|
||||
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<HostsSettings>(HostsModuleName);
|
||||
if (settings != null)
|
||||
{
|
||||
ShowStartupWarning = settings.Properties.ShowStartupWarning;
|
||||
AdditionalLinesPosition = settings.Properties.AdditionalLinesPosition;
|
||||
}
|
||||
|
||||
retry = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (retryCount > MaxNumberOfRetry)
|
||||
{
|
||||
retry = false;
|
||||
}
|
||||
|
||||
Logger.LogError("Failed to read changed settings", ex);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
261
src/modules/Hosts/Hosts/Strings/en-us/Resources.resw
Normal file
261
src/modules/Hosts/Hosts/Strings/en-us/Resources.resw
Normal file
@ -0,0 +1,261 @@
|
||||
<?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="Active.Header" xml:space="preserve">
|
||||
<value>Active</value>
|
||||
</data>
|
||||
<data name="ActiveToggle.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Active</value>
|
||||
</data>
|
||||
<data name="AddBtn" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
</data>
|
||||
<data name="AddEntry.Text" xml:space="preserve">
|
||||
<value>New entry</value>
|
||||
</data>
|
||||
<data name="AddEntryBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>New entry</value>
|
||||
</data>
|
||||
<data name="AdditionalLinesBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Additional lines</value>
|
||||
</data>
|
||||
<data name="AdditionalLinesBtn.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Additional lines</value>
|
||||
</data>
|
||||
<data name="AdditionalLinesDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="AdditionalLinesDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="AdditionalLinesDialog.Title" xml:space="preserve">
|
||||
<value>Additional lines</value>
|
||||
</data>
|
||||
<data name="AddNewEntryDialog_Title" xml:space="preserve">
|
||||
<value>Add new entry</value>
|
||||
</data>
|
||||
<data name="Address.Header" xml:space="preserve">
|
||||
<value>Address</value>
|
||||
<comment>"Address" refers to the IP address of the entry</comment>
|
||||
</data>
|
||||
<data name="AddressFilter.Header" xml:space="preserve">
|
||||
<value>Address</value>
|
||||
<comment>"Address" refers to the IP address of the entry</comment>
|
||||
</data>
|
||||
<data name="ClearFiltersBtn.Content" xml:space="preserve">
|
||||
<value>Clear filters</value>
|
||||
</data>
|
||||
<data name="ClearFiltersBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Clear filters</value>
|
||||
</data>
|
||||
<data name="Comment.Header" xml:space="preserve">
|
||||
<value>Comment</value>
|
||||
<comment>"Comment" refers to the comment of the entry</comment>
|
||||
</data>
|
||||
<data name="CommentFilter.Header" xml:space="preserve">
|
||||
<value>Comment</value>
|
||||
<comment>"Comment" refers to the comment of the entry</comment>
|
||||
</data>
|
||||
<data name="Delete.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="DeleteDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>No</value>
|
||||
</data>
|
||||
<data name="DeleteDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Yes</value>
|
||||
</data>
|
||||
<data name="DeleteDialogAreYouSure.Text" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this entry?</value>
|
||||
</data>
|
||||
<data name="Entries.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Entries</value>
|
||||
</data>
|
||||
<data name="EntryDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="FileChanged.Message" xml:space="preserve">
|
||||
<value>Hosts file has changed by another application. Do you want to reload it?</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="FileSaveError.Message" xml:space="preserve">
|
||||
<value>Failed to save hosts file.</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="FilterBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Filters</value>
|
||||
</data>
|
||||
<data name="FilterBtn.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Filters</value>
|
||||
</data>
|
||||
<data name="Hosts.Header" xml:space="preserve">
|
||||
<value>Hosts</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="Hosts.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Seperate multiple hosts by space (e.g. server server.local).</value>
|
||||
<comment>Do not localize "server" and "server.local"</comment>
|
||||
</data>
|
||||
<data name="HostsFilter.Header" xml:space="preserve">
|
||||
<value>Hosts</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</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="Ping.Text" xml:space="preserve">
|
||||
<value>Ping</value>
|
||||
<comment>"Ping" refers to the command-line utility, do not loc</comment>
|
||||
</data>
|
||||
<data name="Reload.Content" xml:space="preserve">
|
||||
<value>Reload</value>
|
||||
</data>
|
||||
<data name="SettingsBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="SettingsBtn.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="UpdateBtn" xml:space="preserve">
|
||||
<value>Update</value>
|
||||
</data>
|
||||
<data name="UpdateEntry_Title" xml:space="preserve">
|
||||
<value>Update the entry</value>
|
||||
</data>
|
||||
<data name="WarningDialog_AcceptBtn" xml:space="preserve">
|
||||
<value>Accept</value>
|
||||
</data>
|
||||
<data name="WarningDialog_QuitBtn" xml:space="preserve">
|
||||
<value>Quit</value>
|
||||
</data>
|
||||
<data name="WarningDialog_Text" xml:space="preserve">
|
||||
<value>Altering hosts file has direct real world impact of how this computer resolves domain names.</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="WarningDialog_Title" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
<data name="Window.Title" xml:space="preserve">
|
||||
<value>Hosts File Editor</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility. "Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
</root>
|
242
src/modules/Hosts/Hosts/ViewModels/MainViewModel.cs
Normal file
242
src/modules/Hosts/Hosts/ViewModels/MainViewModel.cs
Normal file
@ -0,0 +1,242 @@
|
||||
// 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 Common.UI;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Hosts.Helpers;
|
||||
using Hosts.Models;
|
||||
using Hosts.Settings;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace Hosts.ViewModels
|
||||
{
|
||||
public partial class MainViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly IHostsService _hostsService;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private bool _disposed;
|
||||
|
||||
[ObservableProperty]
|
||||
private Entry _selected;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _error;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _fileChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _addressFilter;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _hostsFilter;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _commentFilter;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _filtered;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _additionalLines;
|
||||
|
||||
private ObservableCollection<Entry> _entries;
|
||||
|
||||
public ObservableCollection<Entry> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filtered)
|
||||
{
|
||||
var filter = _entries.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_addressFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Address.Contains(_addressFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_hostsFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Hosts.Contains(_hostsFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_commentFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Comment.Contains(_commentFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return new ObservableCollection<Entry>(filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _entries;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_entries = value;
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand ReadHostsCommand => new RelayCommand(ReadHosts);
|
||||
|
||||
public ICommand ApplyFiltersCommand => new RelayCommand(ApplyFilters);
|
||||
|
||||
public ICommand ClearFiltersCommand => new RelayCommand(ClearFilters);
|
||||
|
||||
public ICommand OpenSettingsCommand => new RelayCommand(OpenSettings);
|
||||
|
||||
public MainViewModel(
|
||||
IHostsService hostService,
|
||||
IUserSettings userSettings)
|
||||
{
|
||||
_hostsService = hostService;
|
||||
_userSettings = userSettings;
|
||||
|
||||
_hostsService.FileChanged += (s, e) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() => FileChanged = true);
|
||||
};
|
||||
}
|
||||
|
||||
public void Add(Entry entry)
|
||||
{
|
||||
entry.PropertyChanged += Entry_PropertyChanged;
|
||||
_entries.Add(entry);
|
||||
}
|
||||
|
||||
public void Update(int index, Entry entry)
|
||||
{
|
||||
var existingEntry = _entries.ElementAt(index);
|
||||
existingEntry.Address = entry.Address;
|
||||
existingEntry.Comment = entry.Comment;
|
||||
existingEntry.Hosts = entry.Hosts;
|
||||
existingEntry.Active = entry.Active;
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
_entries.Remove(Selected);
|
||||
if (Filtered)
|
||||
{
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAdditionalLines(string lines)
|
||||
{
|
||||
_additionalLines = lines;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var error = !await _hostsService.WriteAsync(_additionalLines, _entries);
|
||||
await _dispatcherQueue.EnqueueAsync(() => Error = error);
|
||||
});
|
||||
}
|
||||
|
||||
public void ReadHosts()
|
||||
{
|
||||
FileChanged = false;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
(_additionalLines, var entries) = await _hostsService.ReadAsync();
|
||||
|
||||
await _dispatcherQueue.EnqueueAsync(() =>
|
||||
{
|
||||
Entries = new ObservableCollection<Entry>(entries);
|
||||
|
||||
foreach (var e in _entries)
|
||||
{
|
||||
e.PropertyChanged += Entry_PropertyChanged;
|
||||
}
|
||||
|
||||
_entries.CollectionChanged += Entries_CollectionChanged;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void ApplyFilters()
|
||||
{
|
||||
if (_entries != null)
|
||||
{
|
||||
Filtered = !string.IsNullOrWhiteSpace(_addressFilter) || !string.IsNullOrWhiteSpace(_hostsFilter) || !string.IsNullOrWhiteSpace(_commentFilter);
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFilters()
|
||||
{
|
||||
AddressFilter = null;
|
||||
HostsFilter = null;
|
||||
CommentFilter = null;
|
||||
}
|
||||
|
||||
public async Task PingSelectedAsync()
|
||||
{
|
||||
var selected = _selected;
|
||||
selected.Ping = null;
|
||||
selected.Pinging = true;
|
||||
selected.Ping = await _hostsService.PingAsync(_selected.Address);
|
||||
selected.Pinging = false;
|
||||
}
|
||||
|
||||
public void OpenSettings()
|
||||
{
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Hosts);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
// Ping should't trigger a file save
|
||||
if (e.PropertyName == nameof(Entry.Ping) || e.PropertyName == nameof(Entry.Pinging))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var error = !await _hostsService.WriteAsync(_additionalLines, _entries);
|
||||
await _dispatcherQueue.EnqueueAsync(() => Error = error);
|
||||
});
|
||||
}
|
||||
|
||||
private void Entries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var error = !await _hostsService.WriteAsync(_additionalLines, _entries);
|
||||
await _dispatcherQueue.EnqueueAsync(() => Error = error);
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_hostsService?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
366
src/modules/Hosts/Hosts/Views/MainPage.xaml
Normal file
366
src/modules/Hosts/Hosts/Views/MainPage.xaml
Normal file
@ -0,0 +1,366 @@
|
||||
<Page
|
||||
x:Class="Hosts.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="using:Hosts.Helpers"
|
||||
xmlns:i="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:local="using:Hosts.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:Hosts.Models"
|
||||
x:Name="Page"
|
||||
Loaded="Page_Loaded"
|
||||
mc:Ignorable="d">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="Loaded">
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ReadHostsCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="64" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid>
|
||||
<Button
|
||||
x:Uid="AddEntryBtn"
|
||||
Height="36"
|
||||
Margin="16,0,0,0"
|
||||
Command="{x:Bind NewDialogCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBlock
|
||||
x:Name="Icon"
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Text="" />
|
||||
<TextBlock x:Uid="AddEntry" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<StackPanel
|
||||
Padding="0,0,16,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
x:Uid="AdditionalLinesBtn"
|
||||
Height="36"
|
||||
Command="{x:Bind AdditionalLinesDialogCommand}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="14"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
x:Uid="FilterBtn"
|
||||
Height="36"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="14"
|
||||
Glyph="" />
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel
|
||||
Width="320"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="12">
|
||||
<AutoSuggestBox
|
||||
x:Uid="AddressFilter"
|
||||
PlaceholderText=""
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.AddressFilter, Mode=TwoWay}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="TextChanged">
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ApplyFiltersCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AutoSuggestBox>
|
||||
<AutoSuggestBox
|
||||
x:Uid="HostsFilter"
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.HostsFilter, Mode=TwoWay}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="TextChanged">
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ApplyFiltersCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AutoSuggestBox>
|
||||
<AutoSuggestBox
|
||||
x:Uid="CommentFilter"
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.CommentFilter, Mode=TwoWay}">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:EventTriggerBehavior EventName="TextChanged">
|
||||
<ic:InvokeCommandAction Command="{x:Bind ViewModel.ApplyFiltersCommand}" />
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AutoSuggestBox>
|
||||
<Button
|
||||
x:Uid="ClearFiltersBtn"
|
||||
Margin="0,6,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind ViewModel.ClearFiltersCommand}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
x:Uid="SettingsBtn"
|
||||
Height="36"
|
||||
Command="{x:Bind ViewModel.OpenSettingsCommand}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="14"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Orientation="Vertical"
|
||||
Visibility="Visible">
|
||||
<InfoBar
|
||||
x:Uid="FileSaveError"
|
||||
CornerRadius="0"
|
||||
IsOpen="{x:Bind ViewModel.Error, Mode=TwoWay}"
|
||||
Severity="Error" />
|
||||
|
||||
<InfoBar
|
||||
x:Uid="FileChanged"
|
||||
IsOpen="{x:Bind ViewModel.FileChanged, Mode=TwoWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.ActionButton>
|
||||
<Button x:Uid="Reload" Command="{x:Bind ViewModel.ReadHostsCommand}" />
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
https://github.com/microsoft/microsoft-ui-xaml/issues/7690
|
||||
AllowDrop="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
CanDragItems="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
CanReorderItems="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
-->
|
||||
<ListView
|
||||
x:Uid="Entries"
|
||||
x:Name="Entries"
|
||||
Grid.Row="1"
|
||||
Margin="16,8,16,16"
|
||||
Background="{ThemeResource LayerFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Entries_ItemClick"
|
||||
ItemsSource="{Binding Entries, Mode=TwoWay}"
|
||||
SelectedItem="{Binding Selected, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:Entry">
|
||||
<Grid
|
||||
AutomationProperties.Name="{x:Bind Address, Mode=OneWay}"
|
||||
Background="Transparent"
|
||||
IsRightTapEnabled="True"
|
||||
RightTapped="Grid_RightTapped">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="Ping"
|
||||
Click="Ping_Click"
|
||||
Icon="TwoBars" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveUp"
|
||||
Click="ReorderButtonUp_Click"
|
||||
IsEnabled="{Binding DataContext.Filtered, ElementName=Page, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveDown"
|
||||
Click="ReorderButtonDown_Click"
|
||||
IsEnabled="{Binding DataContext.Filtered, ElementName=Page, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<MenuFlyoutItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</MenuFlyoutItem.Icon>
|
||||
</MenuFlyoutItem>
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="Delete"
|
||||
Click="Delete_Click"
|
||||
Icon="Delete" />
|
||||
</MenuFlyout>
|
||||
</FlyoutBase.AttachedFlyout>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Address, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind helpers:StringHelper.GetHostsWithComment(Hosts, Comment), Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<ProgressRing
|
||||
Grid.Column="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="0,0,8,0"
|
||||
IsActive="{x:Bind Pinging, Mode=OneWay}" />
|
||||
<FontIcon
|
||||
x:Name="PingIcon"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
FontSize="18"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Visibility="Collapsed">
|
||||
<i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior
|
||||
Binding="{x:Bind Ping, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Glyph"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Visibility"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="Visible" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Foreground"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="{StaticResource SystemFillColorSuccessBrush}" />
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior
|
||||
Binding="{x:Bind Ping, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Glyph"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Visibility"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="Visible" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Foreground"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="{StaticResource SystemFillColorCriticalBrush}" />
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior
|
||||
Binding="{x:Bind Ping, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{x:Null}">
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Visibility"
|
||||
TargetObject="{Binding ElementName=PingIcon}"
|
||||
Value="Collapsed" />
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</FontIcon>
|
||||
<ToggleSwitch
|
||||
x:Uid="ActiveToggle"
|
||||
Grid.Column="3"
|
||||
Width="40"
|
||||
MinWidth="0"
|
||||
HorizontalAlignment="Right"
|
||||
IsOn="{x:Bind Active, Mode=TwoWay}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<ContentDialog
|
||||
x:Name="EntryDialog"
|
||||
x:Uid="EntryDialog"
|
||||
IsPrimaryButtonEnabled="{Binding Valid, Mode=TwoWay}"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<ContentDialog.DataContext>
|
||||
<models:Entry />
|
||||
</ContentDialog.DataContext>
|
||||
<StackPanel
|
||||
MinWidth="480"
|
||||
Margin="0,12,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="24">
|
||||
<TextBox
|
||||
x:Uid="Address"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Address, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox
|
||||
x:Uid="Hosts"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Hosts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox
|
||||
x:Uid="Comment"
|
||||
IsSpellCheckEnabled="False"
|
||||
Text="{Binding Comment, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
x:Uid="Active"
|
||||
IsOn="{Binding Active, Mode=TwoWay}"
|
||||
OffContent=""
|
||||
OnContent="" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Name="DeleteDialog"
|
||||
x:Uid="DeleteDialog"
|
||||
PrimaryButtonCommand="{x:Bind DeleteCommand}"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
<TextBlock x:Uid="DeleteDialogAreYouSure" />
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Name="AdditionalLinesDialog"
|
||||
x:Uid="AdditionalLinesDialog"
|
||||
PrimaryButtonCommand="{x:Bind UpdateAdditionalLinesCommand}"
|
||||
PrimaryButtonStyle="{StaticResource AccentButtonStyle}">
|
||||
|
||||
<TextBox
|
||||
x:Name="AdditionalLines"
|
||||
Height="300"
|
||||
MinWidth="480"
|
||||
Margin="0,12,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
AcceptsReturn="True"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
185
src/modules/Hosts/Hosts/Views/MainPage.xaml.cs
Normal file
185
src/modules/Hosts/Hosts/Views/MainPage.xaml.cs
Normal file
@ -0,0 +1,185 @@
|
||||
// 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.Tasks;
|
||||
using System.Windows.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Hosts.Models;
|
||||
using Hosts.Settings;
|
||||
using Hosts.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace Hosts.Views
|
||||
{
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
public MainViewModel ViewModel { get; private set; }
|
||||
|
||||
public ICommand NewDialogCommand => new AsyncRelayCommand(OpenNewDialogAsync);
|
||||
|
||||
public ICommand AdditionalLinesDialogCommand => new AsyncRelayCommand(OpenAdditionalLinesDialogAsync);
|
||||
|
||||
public ICommand AddCommand => new RelayCommand(Add);
|
||||
|
||||
public ICommand UpdateCommand => new RelayCommand(Update);
|
||||
|
||||
public ICommand DeleteCommand => new RelayCommand(Delete);
|
||||
|
||||
public ICommand UpdateAdditionalLinesCommand => new RelayCommand(UpdateAdditionalLines);
|
||||
|
||||
public ICommand ExitCommand => new RelayCommand(() => { Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread().TryEnqueue(Application.Current.Exit); });
|
||||
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = App.GetService<MainViewModel>();
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
private async Task OpenNewDialogAsync()
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForViewIndependentUse();
|
||||
EntryDialog.Title = resourceLoader.GetString("AddNewEntryDialog_Title");
|
||||
EntryDialog.PrimaryButtonText = resourceLoader.GetString("AddBtn");
|
||||
EntryDialog.PrimaryButtonCommand = AddCommand;
|
||||
EntryDialog.DataContext = new Entry(string.Empty, string.Empty, string.Empty, true);
|
||||
await EntryDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async Task OpenAdditionalLinesDialogAsync()
|
||||
{
|
||||
AdditionalLines.Text = ViewModel.AdditionalLines;
|
||||
await AdditionalLinesDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private async void Entries_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForViewIndependentUse();
|
||||
ViewModel.Selected = e.ClickedItem as Entry;
|
||||
EntryDialog.Title = resourceLoader.GetString("UpdateEntry_Title");
|
||||
EntryDialog.PrimaryButtonText = resourceLoader.GetString("UpdateBtn");
|
||||
EntryDialog.PrimaryButtonCommand = UpdateCommand;
|
||||
var clone = ViewModel.Selected.Clone();
|
||||
EntryDialog.DataContext = clone;
|
||||
await EntryDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void Add()
|
||||
{
|
||||
ViewModel.Add(EntryDialog.DataContext as Entry);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
ViewModel.Update(Entries.SelectedIndex, EntryDialog.DataContext as Entry);
|
||||
}
|
||||
|
||||
private void Delete()
|
||||
{
|
||||
ViewModel.DeleteSelected();
|
||||
}
|
||||
|
||||
private void UpdateAdditionalLines()
|
||||
{
|
||||
ViewModel.UpdateAdditionalLines(AdditionalLines.Text);
|
||||
}
|
||||
|
||||
private void Grid_RightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
var owner = sender as FrameworkElement;
|
||||
if (owner != null)
|
||||
{
|
||||
var flyoutBase = FlyoutBase.GetAttachedFlyout(owner);
|
||||
flyoutBase.ShowAt(owner, new FlyoutShowOptions
|
||||
{
|
||||
Position = e.GetPosition(owner),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void Delete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuFlyoutItem = sender as MenuFlyoutItem;
|
||||
|
||||
if (menuFlyoutItem != null)
|
||||
{
|
||||
var selectedEntry = menuFlyoutItem.DataContext as Entry;
|
||||
ViewModel.Selected = selectedEntry;
|
||||
DeleteDialog.Title = selectedEntry.Address;
|
||||
await DeleteDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void Ping_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuFlyoutItem = sender as MenuFlyoutItem;
|
||||
|
||||
if (menuFlyoutItem != null)
|
||||
{
|
||||
ViewModel.Selected = menuFlyoutItem.DataContext as Entry;
|
||||
await ViewModel.PingSelectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var userSettings = App.GetService<IUserSettings>();
|
||||
if (userSettings.ShowStartupWarning)
|
||||
{
|
||||
var resourceLoader = ResourceLoader.GetForViewIndependentUse();
|
||||
var dialog = new ContentDialog();
|
||||
|
||||
dialog.XamlRoot = XamlRoot;
|
||||
dialog.Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style;
|
||||
dialog.Title = resourceLoader.GetString("WarningDialog_Title");
|
||||
dialog.Content = new TextBlock
|
||||
{
|
||||
Text = resourceLoader.GetString("WarningDialog_Text"),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
};
|
||||
dialog.PrimaryButtonText = resourceLoader.GetString("WarningDialog_AcceptBtn");
|
||||
dialog.PrimaryButtonStyle = Application.Current.Resources["AccentButtonStyle"] as Style;
|
||||
dialog.CloseButtonText = resourceLoader.GetString("WarningDialog_QuitBtn");
|
||||
dialog.CloseButtonCommand = ExitCommand;
|
||||
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReorderButtonUp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuFlyoutItem = sender as MenuFlyoutItem;
|
||||
|
||||
if (menuFlyoutItem != null)
|
||||
{
|
||||
var entry = menuFlyoutItem.DataContext as Entry;
|
||||
var index = ViewModel.Entries.IndexOf(entry);
|
||||
if (index > 0)
|
||||
{
|
||||
ViewModel.Entries.Move(index, index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReorderButtonDown_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuFlyoutItem = sender as MenuFlyoutItem;
|
||||
|
||||
if (menuFlyoutItem != null)
|
||||
{
|
||||
var entry = menuFlyoutItem.DataContext as Entry;
|
||||
var index = ViewModel.Entries.IndexOf(entry);
|
||||
if (index < ViewModel.Entries.Count - 1)
|
||||
{
|
||||
ViewModel.Entries.Move(index, index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/modules/Hosts/Hosts/app.manifest
Normal file
15
src/modules/Hosts/Hosts/app.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?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="Hosts.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
BIN
src/modules/Hosts/Hosts/icon.ico
Normal file
BIN
src/modules/Hosts/Hosts/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
@ -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,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<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 HostsModuleInterface.base.rc HostsModuleInterface.rc" />
|
||||
</Target>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{B41B888C-7DB8-4747-B262-4062E05A230D}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>HostsModuleInterface</RootNamespace>
|
||||
<ProjectName>HostsModuleInterface</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>$(SolutionDir)$(Platform)\$(Configuration)\modules\Hosts\</OutDir>
|
||||
<TargetName>PowerToys.HostsModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(CIBuild)'!='true'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<None Include="resource.base.h" />
|
||||
<ClInclude Include="Generated Files\resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
</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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="HostsModuleInterface.base.rc" />
|
||||
<ResourceCompile Include="Generated Files\HostsModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resource.resx" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
@ -0,0 +1,59 @@
|
||||
<?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>
|
||||
<Filter Include="Generated Files">
|
||||
<UniqueIdentifier>{875a08c6-f610-4667-bd0f-80171ed96072}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="HostsModuleInterface.base.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
<None Include="Resource.resx">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Generated Files\resource.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files\HostsModuleInterface.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
BIN
src/modules/Hosts/HostsModuleInterface/RCa04224
Normal file
BIN
src/modules/Hosts/HostsModuleInterface/RCa04224
Normal file
Binary file not shown.
124
src/modules/Hosts/HostsModuleInterface/Resource.resx
Normal file
124
src/modules/Hosts/HostsModuleInterface/Resource.resx
Normal file
@ -0,0 +1,124 @@
|
||||
<?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="Hosts_Name" xml:space="preserve">
|
||||
<value>Hosts File Editor</value>
|
||||
<comment>"Hosts" refer to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
</root>
|
199
src/modules/Hosts/HostsModuleInterface/dllmain.cpp
Normal file
199
src/modules/Hosts/HostsModuleInterface/dllmain.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "trace.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "Generated Files/resource.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <common/utils/resources.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <string>
|
||||
|
||||
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"Hosts";
|
||||
}
|
||||
|
||||
class HostsModuleInterface : 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;
|
||||
|
||||
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 = (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(L"Starting Hosts 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"modules\\Hosts\\PowerToys.Hosts.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = executable_args.data();
|
||||
|
||||
if (runas)
|
||||
{
|
||||
sei.lpVerb = L"runas";
|
||||
}
|
||||
|
||||
if (ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::trace("Successfully started the Hosts process");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Hosts failed to start. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
|
||||
public:
|
||||
HostsModuleInterface()
|
||||
{
|
||||
app_name = GET_RESOURCE_STRING(IDS_HOSTS_NAME);
|
||||
app_key = ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::hostsLoggerName);
|
||||
}
|
||||
|
||||
~HostsModuleInterface()
|
||||
{
|
||||
m_enabled = false;
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
virtual void destroy() override
|
||||
{
|
||||
Logger::trace("HostsModuleInterface::destroy()");
|
||||
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();
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void call_custom_action(const wchar_t* action) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::CustomActionObject action_object =
|
||||
PowerToysSettings::CustomActionObject::from_json_string(action);
|
||||
|
||||
if (is_process_running())
|
||||
{
|
||||
bring_process_to_front();
|
||||
}
|
||||
else if (action_object.get_name() == L"Launch")
|
||||
{
|
||||
launch_process(false);
|
||||
}
|
||||
else if (action_object.get_name() == L"LaunchAdministrator")
|
||||
{
|
||||
launch_process(true);
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
Logger::error(L"Failed to parse action. {}", action);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
Logger::trace("HostsModuleInterface::enable()");
|
||||
m_enabled = true;
|
||||
};
|
||||
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::trace("HostsModuleInterface::disable()");
|
||||
if (m_enabled)
|
||||
{
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new HostsModuleInterface();
|
||||
}
|
4
src/modules/Hosts/HostsModuleInterface/packages.config
Normal file
4
src/modules/Hosts/HostsModuleInterface/packages.config
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220418.1" targetFramework="native" />
|
||||
</packages>
|
1
src/modules/Hosts/HostsModuleInterface/pch.cpp
Normal file
1
src/modules/Hosts/HostsModuleInterface/pch.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "pch.h"
|
5
src/modules/Hosts/HostsModuleInterface/pch.h
Normal file
5
src/modules/Hosts/HostsModuleInterface/pch.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <ProjectTelemetry.h>
|
13
src/modules/Hosts/HostsModuleInterface/resource.base.h
Normal file
13
src/modules/Hosts/HostsModuleInterface/resource.base.h
Normal file
@ -0,0 +1,13 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by AlwaysOnTopModuleInterface.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys Hosts Module"
|
||||
#define INTERNAL_NAME "PowerToys.HostsModuleInterface"
|
||||
#define ORIGINAL_FILENAME "PowerToys.HostsModuleInterface.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
19
src/modules/Hosts/HostsModuleInterface/trace.cpp
Normal file
19
src/modules/Hosts/HostsModuleInterface/trace.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider() noexcept
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider() noexcept
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
8
src/modules/Hosts/HostsModuleInterface/trace.h
Normal file
8
src/modules/Hosts/HostsModuleInterface/trace.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider() noexcept;
|
||||
static void UnregisterProvider() noexcept;
|
||||
};
|
@ -155,8 +155,8 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"modules/MouseUtils/PowerToys.MousePointerCrosshairs.dll",
|
||||
L"modules/PowerAccent/PowerToys.PowerAccentModuleInterface.dll",
|
||||
L"modules/PowerOCR/PowerToys.PowerOCRModuleInterface.dll",
|
||||
|
||||
L"modules/MeasureTool/PowerToys.MeasureToolModuleInterface.dll",
|
||||
L"modules/Hosts/PowerToys.HostsModuleInterface.dll",
|
||||
};
|
||||
const auto VCM_PATH = L"modules/VideoConference/PowerToys.VideoConferenceModule.dll";
|
||||
if (const auto mf = LoadLibraryA("mf.dll"))
|
||||
|
@ -569,6 +569,8 @@ std::string ESettingsWindowNames_to_string(ESettingsWindowNames value)
|
||||
return "ShortcutGuide";
|
||||
case ESettingsWindowNames::VideoConference:
|
||||
return "VideoConference";
|
||||
case ESettingsWindowNames::Hosts:
|
||||
return "Hosts";
|
||||
default:
|
||||
{
|
||||
Logger::error(L"Can't convert ESettingsWindowNames value={} to string", static_cast<int>(value));
|
||||
@ -628,6 +630,10 @@ ESettingsWindowNames ESettingsWindowNames_from_string(std::string value)
|
||||
{
|
||||
return ESettingsWindowNames::VideoConference;
|
||||
}
|
||||
else if (value == "Hosts")
|
||||
{
|
||||
return ESettingsWindowNames::Hosts;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Can't convert string value={} to ESettingsWindowNames", winrt::to_hstring(value));
|
||||
|
@ -15,7 +15,8 @@ enum class ESettingsWindowNames
|
||||
PowerRename,
|
||||
FileExplorer,
|
||||
ShortcutGuide,
|
||||
VideoConference
|
||||
VideoConference,
|
||||
Hosts
|
||||
};
|
||||
|
||||
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
|
||||
|
@ -287,6 +287,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool hosts = true;
|
||||
|
||||
[JsonPropertyName("Hosts")]
|
||||
public bool Hosts
|
||||
{
|
||||
get => hosts;
|
||||
set
|
||||
{
|
||||
if (hosts != value)
|
||||
{
|
||||
LogTelemetryEvent(value);
|
||||
hosts = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
|
@ -0,0 +1,12 @@
|
||||
// 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 Settings.UI.Library.Enumerations
|
||||
{
|
||||
public enum AdditionalLinesPosition
|
||||
{
|
||||
Top = 0,
|
||||
Bottom = 1,
|
||||
}
|
||||
}
|
27
src/settings-ui/Settings.UI.Library/HostsProperties.cs
Normal file
27
src/settings-ui/Settings.UI.Library/HostsProperties.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class HostsProperties
|
||||
{
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool ShowStartupWarning { get; set; }
|
||||
|
||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||
public bool LaunchAdministrator { get; set; }
|
||||
|
||||
public AdditionalLinesPosition AdditionalLinesPosition { get; set; }
|
||||
|
||||
public HostsProperties()
|
||||
{
|
||||
ShowStartupWarning = true;
|
||||
LaunchAdministrator = true;
|
||||
AdditionalLinesPosition = AdditionalLinesPosition.Top;
|
||||
}
|
||||
}
|
||||
}
|
46
src/settings-ui/Settings.UI.Library/HostsSettings.cs
Normal file
46
src/settings-ui/Settings.UI.Library/HostsSettings.cs
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class HostsSettings : BasePTModuleSettings, ISettingsConfig
|
||||
{
|
||||
public const string ModuleName = "Hosts";
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public HostsProperties Properties { get; set; }
|
||||
|
||||
public HostsSettings()
|
||||
{
|
||||
Properties = new HostsProperties();
|
||||
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;
|
||||
}
|
||||
}
|
115
src/settings-ui/Settings.UI.Library/ViewModels/HostsViewModel.cs
Normal file
115
src/settings-ui/Settings.UI.Library/ViewModels/HostsViewModel.cs
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
|
||||
using Settings.UI.Library.Enumerations;
|
||||
|
||||
namespace Settings.UI.Library.ViewModels
|
||||
{
|
||||
public class HostsViewModel : Observable
|
||||
{
|
||||
private bool _isElevated;
|
||||
|
||||
private ISettingsUtils SettingsUtils { get; set; }
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private HostsSettings Settings { get; set; }
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
public ButtonClickCommand LaunchEventHandler => new ButtonClickCommand(Launch);
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => GeneralSettingsConfig.Enabled.Hosts;
|
||||
|
||||
set
|
||||
{
|
||||
if (value != GeneralSettingsConfig.Enabled.Hosts)
|
||||
{
|
||||
// Set the status in the general settings configuration
|
||||
GeneralSettingsConfig.Enabled.Hosts = value;
|
||||
OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
|
||||
SendConfigMSG(snd.ToString());
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LaunchAdministratorEnabled => IsEnabled && !_isElevated;
|
||||
|
||||
public bool ShowStartupWarning
|
||||
{
|
||||
get => Settings.Properties.ShowStartupWarning;
|
||||
set
|
||||
{
|
||||
if (value != Settings.Properties.ShowStartupWarning)
|
||||
{
|
||||
Settings.Properties.ShowStartupWarning = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool LaunchAdministrator
|
||||
{
|
||||
get => Settings.Properties.LaunchAdministrator;
|
||||
set
|
||||
{
|
||||
if (value != Settings.Properties.LaunchAdministrator)
|
||||
{
|
||||
Settings.Properties.LaunchAdministrator = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int AdditionalLinesPosition
|
||||
{
|
||||
get => (int)Settings.Properties.AdditionalLinesPosition;
|
||||
set
|
||||
{
|
||||
if (value != (int)Settings.Properties.AdditionalLinesPosition)
|
||||
{
|
||||
Settings.Properties.AdditionalLinesPosition = (AdditionalLinesPosition)value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HostsViewModel(ISettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<HostsSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc, bool isElevated)
|
||||
{
|
||||
SettingsUtils = settingsUtils;
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
Settings = moduleSettingsRepository.SettingsConfig;
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
_isElevated = isElevated;
|
||||
}
|
||||
|
||||
public void Launch()
|
||||
{
|
||||
var actionName = "Launch";
|
||||
|
||||
if (!_isElevated && LaunchAdministrator)
|
||||
{
|
||||
actionName = "LaunchAdministrator";
|
||||
}
|
||||
|
||||
SendConfigMSG("{\"action\":{\"Hosts\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
SettingsUtils.SaveSettings(Settings.ToJsonString(), HostsSettings.ModuleName);
|
||||
}
|
||||
}
|
||||
}
|
@ -129,6 +129,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "TextExtractor": StartupPage = typeof(Views.PowerOcrPage); break;
|
||||
case "VideoConference": StartupPage = typeof(Views.VideoConferencePage); break;
|
||||
case "MeasureTool": StartupPage = typeof(Views.MeasureToolPage); break;
|
||||
case "Hosts": StartupPage = typeof(Views.HostsPage); break;
|
||||
default: Debug.Assert(false, "Unexpected SettingsWindow argument value"); break;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
||||
TextExtractor,
|
||||
VideoConference,
|
||||
MeasureTool,
|
||||
Hosts,
|
||||
WhatsNew,
|
||||
}
|
||||
}
|
||||
|
28
src/settings-ui/Settings.UI/OOBE/Views/OobeHosts.xaml
Normal file
28
src/settings-ui/Settings.UI/OOBE/Views/OobeHosts.xaml
Normal file
@ -0,0 +1,28 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobeHosts"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:toolkitcontrols="using:CommunityToolkit.WinUI.UI.Controls">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_Hosts"
|
||||
HeroImage="ms-appx:///Assets/Modules/OOBE/AlwaysOnTop.png">
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,24,0,0">
|
||||
<Button x:Uid="Launch_Hosts" Style="{StaticResource AccentButtonStyle}" Click="Launch_Hosts_Click" />
|
||||
<Button x:Uid="OOBE_Settings"
|
||||
Click="Launch_Settings_Click" />
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_HostsFileEditor" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_Hosts"
|
||||
TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
58
src/settings-ui/Settings.UI/OOBE/Views/OobeHosts.xaml.cs
Normal file
58
src/settings-ui/Settings.UI/OOBE/Views/OobeHosts.xaml.cs
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.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 OobeHosts : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; }
|
||||
|
||||
public OobeHosts()
|
||||
{
|
||||
InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.Hosts]);
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogOpeningModuleEvent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogClosingModuleEvent();
|
||||
}
|
||||
|
||||
private void Launch_Hosts_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
bool launchAdmin = SettingsRepository<HostsSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator;
|
||||
var actionName = "Launch";
|
||||
|
||||
if (!App.IsElevated && launchAdmin)
|
||||
{
|
||||
actionName = "LaunchAdministrator";
|
||||
}
|
||||
|
||||
ShellPage.SendDefaultIPCMessage("{\"action\":{\"Hosts\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
|
||||
}
|
||||
|
||||
private void Launch_Settings_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(HostsPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
}
|
||||
}
|
@ -75,6 +75,13 @@
|
||||
ShowAsMonochrome="False" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_Hosts" Tag="Hosts">
|
||||
<NavigationViewItem.Icon>
|
||||
<BitmapIcon
|
||||
UriSource="ms-appx:///Assets/FluentIcons/FluentIconsAlwaysOnTop.png"
|
||||
ShowAsMonochrome="False" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
<NavigationViewItem x:Uid="Shell_ImageResizer" Tag="ImageResizer">
|
||||
<NavigationViewItem.Icon>
|
||||
<BitmapIcon
|
||||
|
@ -139,6 +139,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
IsNew = true,
|
||||
});
|
||||
|
||||
Modules.Insert((int)PowerToysModules.Hosts, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "Hosts",
|
||||
IsNew = true,
|
||||
});
|
||||
|
||||
Modules.Insert((int)PowerToysModules.WhatsNew, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "WhatsNew",
|
||||
@ -192,6 +198,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
case "VideoConference": NavigationFrame.Navigate(typeof(OobeVideoConference)); break;
|
||||
case "MouseUtils": NavigationFrame.Navigate(typeof(OobeMouseUtils)); break;
|
||||
case "MeasureTool": NavigationFrame.Navigate(typeof(OobeMeasureTool)); break;
|
||||
case "Hosts": NavigationFrame.Navigate(typeof(OobeHosts)); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,10 @@
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="OOBE\Views\OobeHosts.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
@ -68,6 +72,9 @@
|
||||
<None Update="icon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<Page Update="OOBE\Views\OobeHosts.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="OOBE\Views\OobePowerOCR.xaml">
|
||||
|
@ -2554,4 +2554,72 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="QuickAccent_SelectedLanguage_Italian.Content" xml:space="preserve">
|
||||
<value>Italian</value>
|
||||
</data>
|
||||
<data name="Hosts.ModuleDescription" xml:space="preserve">
|
||||
<value>Quick and simple utility for managing hosts file.</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="Hosts.ModuleTitle" xml:space="preserve">
|
||||
<value>Hosts File Editor</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="Shell_Hosts.Content" xml:space="preserve">
|
||||
<value>Hosts File Editor</value>
|
||||
<comment>Products name: Navigation view item name for Hosts File Editor</comment>
|
||||
</data>
|
||||
<data name="Hosts_EnableToggleControl_HeaderText.Header" xml:space="preserve">
|
||||
<value>Enable Hosts File Editor</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="Hosts_Toggle_ShowStartupWarning.Header" xml:space="preserve">
|
||||
<value>Show a warning at startup</value>
|
||||
</data>
|
||||
<data name="Hosts_Activation_GroupSettings.Header" xml:space="preserve">
|
||||
<value>Activation</value>
|
||||
</data>
|
||||
<data name="Hosts_LaunchButtonControl.Description" xml:space="preserve">
|
||||
<value>Manage your hosts file</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="Hosts_LaunchButtonControl.Header" xml:space="preserve">
|
||||
<value>Launch Host File Editor</value>
|
||||
<comment>"Host File Editor" is a product name</comment>
|
||||
</data>
|
||||
<data name="Hosts_LaunchButton_Accessible.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Launch Host File Editor</value>
|
||||
<comment>"Host File Editor" is a product name</comment>
|
||||
</data>
|
||||
<data name="Hosts_AdditionalLinesPosition.Header" xml:space="preserve">
|
||||
<value>Additional lines position</value>
|
||||
</data>
|
||||
<data name="Hosts_AdditionalLinesPosition_Bottom.Content" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="Hosts_AdditionalLinesPosition_Top.Content" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
<data name="Hosts_File_GroupSettings.Header" xml:space="preserve">
|
||||
<value>File</value>
|
||||
</data>
|
||||
<data name="Launch_Hosts.Content" xml:space="preserve">
|
||||
<value>Launch Hosts File Editor</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="LearnMore_Hosts.Text" xml:space="preserve">
|
||||
<value>Learn more about Hosts File Editor</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="Oobe_Hosts.Description" xml:space="preserve">
|
||||
<value>Hosts File Editor is a quick and simple utility for managing hosts file.</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="Oobe_Hosts.Title" xml:space="preserve">
|
||||
<value>Hosts File Editor</value>
|
||||
<comment>"Hosts File Editor" is the name of the utility</comment>
|
||||
</data>
|
||||
<data name="Hosts_Toggle_LaunchAdministrator.Description" xml:space="preserve">
|
||||
<value>Needs to be launched as administrator in order to make changes to the hosts file</value>
|
||||
</data>
|
||||
<data name="Hosts_Toggle_LaunchAdministrator.Header" xml:space="preserve">
|
||||
<value>Launch as administrator</value>
|
||||
</data>
|
||||
</root>
|
79
src/settings-ui/Settings.UI/Views/HostsPage.xaml
Normal file
79
src/settings-ui/Settings.UI/Views/HostsPage.xaml
Normal file
@ -0,0 +1,79 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.HostsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<controls:SettingsPageControl x:Uid="Hosts" IsTabStop="False"
|
||||
ModuleImageSource="ms-appx:///Assets/Modules/AlwaysOnTop.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<controls:Setting x:Uid="Hosts_EnableToggleControl_HeaderText">
|
||||
<controls:Setting.Icon>
|
||||
<BitmapIcon UriSource="ms-appx:///Assets/FluentIcons/FluentIconsAlwaysOnTop.png" ShowAsMonochrome="False" />
|
||||
</controls:Setting.Icon>
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" x:Uid="ToggleSwitch"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Hosts_Activation_GroupSettings"
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
<Button x:Uid="Hosts_LaunchButton_Accessible"
|
||||
Style="{StaticResource SettingButtonStyle}"
|
||||
Command="{x:Bind ViewModel.LaunchEventHandler}">
|
||||
<controls:Setting x:Uid="Hosts_LaunchButtonControl"
|
||||
Style="{StaticResource ExpanderHeaderSettingStyle}"
|
||||
Icon="">
|
||||
<controls:Setting.ActionContent>
|
||||
<FontIcon Glyph=""
|
||||
FontSize="18"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
</Button>
|
||||
|
||||
<controls:Setting x:Uid="Hosts_Toggle_LaunchAdministrator"
|
||||
Icon=""
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.LaunchAdministratorEnabled}">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.LaunchAdministrator}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:Setting x:Uid="Hosts_Toggle_ShowStartupWarning"
|
||||
Icon=""
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind Mode=TwoWay, Path=ViewModel.ShowStartupWarning}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Hosts_File_GroupSettings"
|
||||
IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
<controls:Setting x:Uid="Hosts_AdditionalLinesPosition" Icon="" >
|
||||
<controls:Setting.ActionContent>
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SelectedIndex="{x:Bind Path=ViewModel.AdditionalLinesPosition, Mode=TwoWay}" >
|
||||
<ComboBoxItem x:Uid="Hosts_AdditionalLinesPosition_Top" />
|
||||
<ComboBoxItem x:Uid="Hosts_AdditionalLinesPosition_Bottom" />
|
||||
</ComboBox>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_Hosts" Link="https://aka.ms/PowerToysOverview_HostsFileEditor"/>
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
</Page>
|
22
src/settings-ui/Settings.UI/Views/HostsPage.xaml.cs
Normal file
22
src/settings-ui/Settings.UI/Views/HostsPage.xaml.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Settings.UI.Library.ViewModels;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
public sealed partial class HostsPage : Page
|
||||
{
|
||||
private HostsViewModel ViewModel { get; }
|
||||
|
||||
public HostsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
var settingsUtils = new SettingsUtils();
|
||||
ViewModel = new HostsViewModel(settingsUtils, SettingsRepository<GeneralSettings>.GetInstance(settingsUtils), SettingsRepository<HostsSettings>.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage, App.IsElevated);
|
||||
}
|
||||
}
|
||||
}
|
@ -76,6 +76,12 @@
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
<NavigationViewItem x:Uid="Shell_Hosts" helpers:NavHelper.NavigateTo="views:HostsPage" >
|
||||
<NavigationViewItem.Icon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="ms-appx:///Assets/FluentIcons/FluentIconsSettings.png" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
<NavigationViewItem x:Uid="Shell_ImageResizer" helpers:NavHelper.NavigateTo="views:ImageResizerPage">
|
||||
<NavigationViewItem.Icon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="ms-appx:///Assets/FluentIcons/FluentIconsImageResizer.png" />
|
||||
@ -137,9 +143,6 @@
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="ms-appx:///Assets/FluentIcons/FluentIconsVideoConferenceMute.png" />
|
||||
</NavigationViewItem.Icon>
|
||||
</NavigationViewItem>
|
||||
|
||||
|
||||
|
||||
</NavigationView.MenuItems>
|
||||
<NavigationView.PaneFooter>
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
@ -20,5 +20,6 @@ std::vector<std::wstring> processes =
|
||||
L"PowerToys.ImageResizer.exe",
|
||||
L"PowerToys.Update.exe",
|
||||
L"PowerToys.ActionRunner.exe",
|
||||
L"PowerToys.AlwaysOnTop.exe"
|
||||
L"PowerToys.AlwaysOnTop.exe",
|
||||
L"PowerToys.Hosts.exe"
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user