mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-23 02:27:29 +00:00
## Summary of the Pull Request Adds a new Hosts File Editor setting “No leading spaces” that prevents prepending spaces to active lines when saving the hosts file (when any entry is disabled). Default is Off to preserve current behavior. ## PR Checklist - [x] Closes: #36386 - [ ] Communication: N/A (small, scoped option) - [x] Tests: Added/updated and all pass - [x] Localization: New en-US strings added; other locales handled by loc pipeline - [ ] Dev docs: N/A - [x] New binaries: None - [x] Documentation updated: N/A ## Detailed Description of the Pull Request / Additional comments - Settings surface: - `src/settings-ui/Settings.UI.Library/HostsProperties.cs`: add `NoLeadingSpaces` - `src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs`: add `NoLeadingSpaces` - `src/modules/Hosts/Hosts/Settings/UserSettings.cs`: load/save value from settings.json - `src/settings-ui/Settings.UI/ViewModels/HostsViewModel.cs`: expose `NoLeadingSpaces` - `src/settings-ui/Settings.UI/SettingsXAML/Views/HostsPage.xaml`: new SettingsCard toggle - `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`: add `Hosts_NoLeadingSpaces.Header/Description` - Writer change: - `src/modules/Hosts/HostsUILib/Helpers/HostsService.cs`: gate indent with `anyDisabled && !_userSettings.NoLeadingSpaces` - Tests: - `src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs`: `NoLeadingSpaces_Disabled_RemovesIndent` Backward compatibility: default Off, current formatting unchanged unless the user enables the option. ## Validation Steps Performed - Automated: `HostsEditor.UnitTests` including `NoLeadingSpaces_Disabled_RemovesIndent` passing. - Manual: 1. Run PowerToys (runner) as Admin. 2. Settings → Hosts File Editor → enable “No leading spaces”. 3. In editor, add active `127.0.0.10 example1` and disabled `127.0.0.11 example2`; Save. 4. Open `C:\Windows\System32\drivers\etc\hosts` in Notepad. - ON: active line starts at column 0; disabled is `# 127...`. - OFF: active line begins with two spaces when a disabled entry exists.
197 lines
6.4 KiB
C#
197 lines
6.4 KiB
C#
// 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 System.Threading;
|
|
|
|
using global::PowerToys.GPOWrapper;
|
|
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 PowerToys.Interop;
|
|
using Settings.UI.Library.Enumerations;
|
|
|
|
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|
{
|
|
public partial class HostsViewModel : Observable
|
|
{
|
|
private bool _isElevated;
|
|
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
|
private bool _enabledStateIsGPOConfigured;
|
|
private bool _isEnabled;
|
|
|
|
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 => _isEnabled;
|
|
|
|
set
|
|
{
|
|
if (_enabledStateIsGPOConfigured)
|
|
{
|
|
// If it's GPO configured, shouldn't be able to change this state.
|
|
return;
|
|
}
|
|
|
|
if (value != _isEnabled)
|
|
{
|
|
_isEnabled = value;
|
|
|
|
// 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 IsEnabledGpoConfigured
|
|
{
|
|
get => _enabledStateIsGPOConfigured;
|
|
}
|
|
|
|
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 LoopbackDuplicates
|
|
{
|
|
get => Settings.Properties.LoopbackDuplicates;
|
|
set
|
|
{
|
|
if (value != Settings.Properties.LoopbackDuplicates)
|
|
{
|
|
Settings.Properties.LoopbackDuplicates = value;
|
|
NotifyPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool LaunchAdministrator
|
|
{
|
|
get => Settings.Properties.LaunchAdministrator;
|
|
set
|
|
{
|
|
if (value != Settings.Properties.LaunchAdministrator)
|
|
{
|
|
Settings.Properties.LaunchAdministrator = value;
|
|
NotifyPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool NoLeadingSpaces
|
|
{
|
|
get => Settings.Properties.NoLeadingSpaces;
|
|
set
|
|
{
|
|
if (value != Settings.Properties.NoLeadingSpaces)
|
|
{
|
|
Settings.Properties.NoLeadingSpaces = value;
|
|
NotifyPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int AdditionalLinesPosition
|
|
{
|
|
get => (int)Settings.Properties.AdditionalLinesPosition;
|
|
set
|
|
{
|
|
if (value != (int)Settings.Properties.AdditionalLinesPosition)
|
|
{
|
|
Settings.Properties.AdditionalLinesPosition = (HostsAdditionalLinesPosition)value;
|
|
NotifyPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int Encoding
|
|
{
|
|
get => (int)Settings.Properties.Encoding;
|
|
set
|
|
{
|
|
if (value != (int)Settings.Properties.Encoding)
|
|
{
|
|
Settings.Properties.Encoding = (HostsEncoding)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;
|
|
InitializeEnabledValue();
|
|
}
|
|
|
|
private void InitializeEnabledValue()
|
|
{
|
|
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredHostsFileEditorEnabledValue();
|
|
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
|
{
|
|
// Get the enabled state from GPO.
|
|
_enabledStateIsGPOConfigured = true;
|
|
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
|
}
|
|
else
|
|
{
|
|
_isEnabled = GeneralSettingsConfig.Enabled.Hosts;
|
|
}
|
|
}
|
|
|
|
public void Launch()
|
|
{
|
|
string eventName = !_isElevated && LaunchAdministrator
|
|
? Constants.ShowHostsAdminSharedEvent()
|
|
: Constants.ShowHostsSharedEvent();
|
|
|
|
using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
|
|
{
|
|
eventHandle.Set();
|
|
}
|
|
}
|
|
|
|
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
|
|
{
|
|
OnPropertyChanged(propertyName);
|
|
SettingsUtils.SaveSettings(Settings.ToJsonString(), HostsSettings.ModuleName);
|
|
}
|
|
|
|
public void RefreshEnabledState()
|
|
{
|
|
InitializeEnabledValue();
|
|
OnPropertyChanged(nameof(IsEnabled));
|
|
}
|
|
}
|
|
}
|