mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-29 13:37:43 +00:00
Merge remote-tracking branch 'origin/main' into dev/snickler/net10-upgrade
This commit is contained in:
commit
c92eb60655
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@ -637,6 +637,7 @@ hmodule
|
|||||||
hmonitor
|
hmonitor
|
||||||
homies
|
homies
|
||||||
homljgmgpmcbpjbnjpfijnhipfkiclkd
|
homljgmgpmcbpjbnjpfijnhipfkiclkd
|
||||||
|
HOOKPROC
|
||||||
HORZRES
|
HORZRES
|
||||||
HORZSIZE
|
HORZSIZE
|
||||||
Hostbackdropbrush
|
Hostbackdropbrush
|
||||||
|
@ -52,7 +52,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
|
|||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- **Follow the pattern of what you already see in the code.**
|
- **Follow the pattern of what you already see in the code.**
|
||||||
- [Coding style](development/style.md).
|
- [Coding style](style.md).
|
||||||
- Try to package new functionality/components into libraries that have nicely defined interfaces.
|
- Try to package new functionality/components into libraries that have nicely defined interfaces.
|
||||||
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
|
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
|
||||||
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
|
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
|
||||||
|
@ -298,5 +298,34 @@ namespace Hosts.Tests
|
|||||||
var hidden = fileSystem.FileInfo.New(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
|
var hidden = fileSystem.FileInfo.New(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
|
||||||
Assert.IsTrue(hidden);
|
Assert.IsTrue(hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task NoLeadingSpaces_Disabled_RemovesIndent()
|
||||||
|
{
|
||||||
|
var content =
|
||||||
|
@"10.1.1.1 host host.local # comment
|
||||||
|
10.1.1.2 host2 host2.local # another comment
|
||||||
|
";
|
||||||
|
|
||||||
|
var expected =
|
||||||
|
@"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 fs = new CustomMockFileSystem();
|
||||||
|
var settings = new Mock<IUserSettings>();
|
||||||
|
settings.Setup(s => s.NoLeadingSpaces).Returns(true);
|
||||||
|
var svc = new HostsService(fs, settings.Object, _elevationHelper.Object);
|
||||||
|
fs.AddFile(svc.HostsFilePath, new MockFileData(content));
|
||||||
|
|
||||||
|
var data = await svc.ReadAsync();
|
||||||
|
var entries = data.Entries.ToList();
|
||||||
|
entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
|
||||||
|
await svc.WriteAsync(data.AdditionalLines, entries);
|
||||||
|
|
||||||
|
var result = fs.GetFile(svc.HostsFilePath);
|
||||||
|
Assert.AreEqual(expected, result.TextContents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ namespace Hosts.Settings
|
|||||||
|
|
||||||
private bool _loopbackDuplicates;
|
private bool _loopbackDuplicates;
|
||||||
|
|
||||||
|
public bool NoLeadingSpaces { get; private set; }
|
||||||
|
|
||||||
public bool LoopbackDuplicates
|
public bool LoopbackDuplicates
|
||||||
{
|
{
|
||||||
get => _loopbackDuplicates;
|
get => _loopbackDuplicates;
|
||||||
@ -88,6 +90,7 @@ namespace Hosts.Settings
|
|||||||
AdditionalLinesPosition = (HostsAdditionalLinesPosition)settings.Properties.AdditionalLinesPosition;
|
AdditionalLinesPosition = (HostsAdditionalLinesPosition)settings.Properties.AdditionalLinesPosition;
|
||||||
Encoding = (HostsEncoding)settings.Properties.Encoding;
|
Encoding = (HostsEncoding)settings.Properties.Encoding;
|
||||||
LoopbackDuplicates = settings.Properties.LoopbackDuplicates;
|
LoopbackDuplicates = settings.Properties.LoopbackDuplicates;
|
||||||
|
NoLeadingSpaces = settings.Properties.NoLeadingSpaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry = false;
|
retry = false;
|
||||||
|
@ -157,7 +157,7 @@ namespace HostsUILib.Helpers
|
|||||||
{
|
{
|
||||||
lineBuilder.Append('#').Append(' ');
|
lineBuilder.Append('#').Append(' ');
|
||||||
}
|
}
|
||||||
else if (anyDisabled)
|
else if (anyDisabled && !_userSettings.NoLeadingSpaces)
|
||||||
{
|
{
|
||||||
lineBuilder.Append(' ').Append(' ');
|
lineBuilder.Append(' ').Append(' ');
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,7 @@ namespace HostsUILib.Settings
|
|||||||
event EventHandler LoopbackDuplicatesChanged;
|
event EventHandler LoopbackDuplicatesChanged;
|
||||||
|
|
||||||
public delegate void OpenSettingsFunction();
|
public delegate void OpenSettingsFunction();
|
||||||
|
|
||||||
|
public bool NoLeadingSpaces { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
|
|
||||||
private Task? _initializeItemsTask;
|
private Task? _initializeItemsTask;
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
|
private CancellationTokenSource? _fetchItemsCancellationTokenSource;
|
||||||
|
|
||||||
private ListItemViewModel? _lastSelectedItem;
|
private ListItemViewModel? _lastSelectedItem;
|
||||||
|
|
||||||
@ -129,22 +130,38 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||||
private void FetchItems()
|
private void FetchItems()
|
||||||
{
|
{
|
||||||
|
// Cancel any previous FetchItems operation
|
||||||
|
_fetchItemsCancellationTokenSource?.Cancel();
|
||||||
|
_fetchItemsCancellationTokenSource?.Dispose();
|
||||||
|
_fetchItemsCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var cancellationToken = _fetchItemsCancellationTokenSource.Token;
|
||||||
|
|
||||||
// TEMPORARY: just plop all the items into a single group
|
// TEMPORARY: just plop all the items into a single group
|
||||||
// see 9806fe5d8 for the last commit that had this with sections
|
// see 9806fe5d8 for the last commit that had this with sections
|
||||||
_isFetching = true;
|
_isFetching = true;
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var newItems = _model.Unsafe!.GetItems();
|
|
||||||
|
|
||||||
// Collect all the items into new viewmodels
|
// Collect all the items into new viewmodels
|
||||||
Collection<ListItemViewModel> newViewModels = [];
|
Collection<ListItemViewModel> newViewModels = [];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check for cancellation before starting expensive operations
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var newItems = _model.Unsafe!.GetItems();
|
||||||
|
|
||||||
|
// Check for cancellation after getting items from extension
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// TODO we can probably further optimize this by also keeping a
|
// TODO we can probably further optimize this by also keeping a
|
||||||
// HashSet of every ExtensionObject we currently have, and only
|
// HashSet of every ExtensionObject we currently have, and only
|
||||||
// building new viewmodels for the ones we haven't already built.
|
// building new viewmodels for the ones we haven't already built.
|
||||||
foreach (var item in newItems)
|
foreach (var item in newItems)
|
||||||
{
|
{
|
||||||
|
// Check for cancellation during item processing
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
ListItemViewModel viewModel = new(item, new(this));
|
ListItemViewModel viewModel = new(item, new(this));
|
||||||
|
|
||||||
// If an item fails to load, silently ignore it.
|
// If an item fails to load, silently ignore it.
|
||||||
@ -154,25 +171,57 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for cancellation before initializing first twenty items
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var firstTwenty = newViewModels.Take(20);
|
var firstTwenty = newViewModels.Take(20);
|
||||||
foreach (var item in firstTwenty)
|
foreach (var item in firstTwenty)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
item?.SafeInitializeProperties();
|
item?.SafeInitializeProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel any ongoing search
|
// Cancel any ongoing search
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
|
// Check for cancellation before updating the list
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
List<ListItemViewModel> removedItems = [];
|
||||||
lock (_listLock)
|
lock (_listLock)
|
||||||
{
|
{
|
||||||
// Now that we have new ViewModels for everything from the
|
// Now that we have new ViewModels for everything from the
|
||||||
// extension, smartly update our list of VMs
|
// extension, smartly update our list of VMs
|
||||||
ListHelpers.InPlaceUpdateList(Items, newViewModels);
|
ListHelpers.InPlaceUpdateList(Items, newViewModels, out removedItems);
|
||||||
|
|
||||||
|
// DO NOT ThrowIfCancellationRequested AFTER THIS! If you do,
|
||||||
|
// you'll clean up list items that we've now transferred into
|
||||||
|
// .Items
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we removed items, we need to clean them up, to remove our event handlers
|
||||||
|
foreach (var removedItem in removedItems)
|
||||||
|
{
|
||||||
|
removedItem.SafeCleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Iterate over everything in Items, and prune items from the
|
// TODO: Iterate over everything in Items, and prune items from the
|
||||||
// cache if we don't need them anymore
|
// cache if we don't need them anymore
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Cancellation is expected, don't treat as error
|
||||||
|
|
||||||
|
// However, if we were cancelled, we didn't actually add these items to
|
||||||
|
// our Items list. Before we release them to the GC, make sure we clean
|
||||||
|
// them up
|
||||||
|
foreach (var vm in newViewModels)
|
||||||
|
{
|
||||||
|
vm.SafeCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// TODO: Move this within the for loop, so we can catch issues with individual items
|
// TODO: Move this within the for loop, so we can catch issues with individual items
|
||||||
@ -560,6 +609,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
|
|
||||||
|
_fetchItemsCancellationTokenSource?.Cancel();
|
||||||
|
_fetchItemsCancellationTokenSource?.Dispose();
|
||||||
|
_fetchItemsCancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UnsafeCleanup()
|
protected override void UnsafeCleanup()
|
||||||
@ -570,6 +623,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
EmptyContent = new(new(null), PageContext); // necessary?
|
EmptyContent = new(new(null), PageContext); // necessary?
|
||||||
|
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
|
_fetchItemsCancellationTokenSource?.Cancel();
|
||||||
|
|
||||||
lock (_listLock)
|
lock (_listLock)
|
||||||
{
|
{
|
||||||
|
@ -47,7 +47,8 @@
|
|||||||
<Flyout
|
<Flyout
|
||||||
x:Name="ContextMenuFlyout"
|
x:Name="ContextMenuFlyout"
|
||||||
FlyoutPresenterStyle="{StaticResource ContextMenuFlyoutStyle}"
|
FlyoutPresenterStyle="{StaticResource ContextMenuFlyoutStyle}"
|
||||||
Opened="ContextMenuFlyout_Opened">
|
Opened="ContextMenuFlyout_Opened"
|
||||||
|
ShouldConstrainToRootBounds="False">
|
||||||
<cpcontrols:ContextMenu x:Name="ContextControl" />
|
<cpcontrols:ContextMenu x:Name="ContextControl" />
|
||||||
</Flyout>
|
</Flyout>
|
||||||
|
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
// 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 ManagedCommon;
|
||||||
|
|
||||||
|
using Windows.System;
|
||||||
|
using Windows.Win32;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A class that listens for local keyboard events using a Windows hook.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class LocalKeyboardListener : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is raised when a key is pressed down.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<LocalKeyboardListenerKeyPressedEventArgs>? KeyPressed;
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
private UnhookWindowsHookExSafeHandle? _handle;
|
||||||
|
private HOOKPROC? _hookProc; // Keep reference to prevent GC collection
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a global keyboard hook to listen for key down events.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Throws if the hook could not be registered, which may happen if the system is unable to set the hook.
|
||||||
|
/// </exception>
|
||||||
|
public void RegisterKeyboardHook()
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
if (_handle is not null && !_handle.IsInvalid)
|
||||||
|
{
|
||||||
|
// Hook is already set
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hookProc = KeyEventHook;
|
||||||
|
if (!SetWindowKeyHook(_hookProc))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to register keyboard hook.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to register a global keyboard hook to listen for key down events.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the keyboard hook was successfully registered; otherwise, <see langword="false"/>.
|
||||||
|
/// </returns>
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RegisterKeyboardHook();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed to register hook", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterKeyboardHook()
|
||||||
|
{
|
||||||
|
if (_handle is not null && !_handle.IsInvalid)
|
||||||
|
{
|
||||||
|
// The SafeHandle should automatically call UnhookWindowsHookEx when disposed
|
||||||
|
_handle.Dispose();
|
||||||
|
_handle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hookProc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SetWindowKeyHook(HOOKPROC hookProc)
|
||||||
|
{
|
||||||
|
if (_handle is not null && !_handle.IsInvalid)
|
||||||
|
{
|
||||||
|
// Hook is already set
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handle = PInvoke.SetWindowsHookEx(
|
||||||
|
WINDOWS_HOOK_ID.WH_KEYBOARD,
|
||||||
|
hookProc,
|
||||||
|
PInvoke.GetModuleHandle(null),
|
||||||
|
PInvoke.GetCurrentThreadId());
|
||||||
|
|
||||||
|
// Check if the hook was successfully set
|
||||||
|
return _handle is not null && !_handle.IsInvalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsKeyDownHook(LPARAM lParam)
|
||||||
|
{
|
||||||
|
// The 30th bit tells what the previous key state is with 0 being the "UP" state
|
||||||
|
// For more info see https://learn.microsoft.com/windows/win32/winmsg/keyboardproc#lparam-in
|
||||||
|
return ((lParam.Value >> 30) & 1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LRESULT KeyEventHook(int nCode, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (nCode >= 0 && IsKeyDownHook(lParam))
|
||||||
|
{
|
||||||
|
InvokeKeyDown((VirtualKey)wParam.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError("Failed when invoking key down keyboard hook event", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call next hook in chain - pass null as first parameter for current hook
|
||||||
|
return PInvoke.CallNextHookEx(null, nCode, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeKeyDown(VirtualKey virtualKey)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
KeyPressed?.Invoke(this, new LocalKeyboardListenerKeyPressedEventArgs(virtualKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
UnregisterKeyboardHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
|
||||||
|
using Windows.System;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.Helpers;
|
||||||
|
|
||||||
|
public class LocalKeyboardListenerKeyPressedEventArgs(VirtualKey key) : EventArgs
|
||||||
|
{
|
||||||
|
public VirtualKey Key { get; } = key;
|
||||||
|
}
|
@ -27,6 +27,7 @@ using Microsoft.Windows.AppLifecycle;
|
|||||||
using Windows.ApplicationModel.Activation;
|
using Windows.ApplicationModel.Activation;
|
||||||
using Windows.Foundation;
|
using Windows.Foundation;
|
||||||
using Windows.Graphics;
|
using Windows.Graphics;
|
||||||
|
using Windows.System;
|
||||||
using Windows.UI;
|
using Windows.UI;
|
||||||
using Windows.UI.WindowManagement;
|
using Windows.UI.WindowManagement;
|
||||||
using Windows.Win32;
|
using Windows.Win32;
|
||||||
@ -44,7 +45,8 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
IRecipient<DismissMessage>,
|
IRecipient<DismissMessage>,
|
||||||
IRecipient<ShowWindowMessage>,
|
IRecipient<ShowWindowMessage>,
|
||||||
IRecipient<HideWindowMessage>,
|
IRecipient<HideWindowMessage>,
|
||||||
IRecipient<QuitMessage>
|
IRecipient<QuitMessage>,
|
||||||
|
IDisposable
|
||||||
{
|
{
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")]
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_")]
|
||||||
@ -54,6 +56,7 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
private readonly WNDPROC? _originalWndProc;
|
private readonly WNDPROC? _originalWndProc;
|
||||||
private readonly List<TopLevelHotkey> _hotkeys = [];
|
private readonly List<TopLevelHotkey> _hotkeys = [];
|
||||||
private readonly KeyboardListener _keyboardListener;
|
private readonly KeyboardListener _keyboardListener;
|
||||||
|
private readonly LocalKeyboardListener _localKeyboardListener;
|
||||||
private bool _ignoreHotKeyWhenFullScreen = true;
|
private bool _ignoreHotKeyWhenFullScreen = true;
|
||||||
|
|
||||||
private DesktopAcrylicController? _acrylicController;
|
private DesktopAcrylicController? _acrylicController;
|
||||||
@ -116,6 +119,18 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
{
|
{
|
||||||
Summon(string.Empty);
|
Summon(string.Empty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_localKeyboardListener = new LocalKeyboardListener();
|
||||||
|
_localKeyboardListener.KeyPressed += LocalKeyboardListener_OnKeyPressed;
|
||||||
|
_localKeyboardListener.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == VirtualKey.GoBack)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new GoBackMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings();
|
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings();
|
||||||
@ -376,6 +391,7 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
|
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
|
||||||
// Workaround by turning it off before shutdown.
|
// Workaround by turning it off before shutdown.
|
||||||
App.Current.DebugSettings.FailFastOnErrors = false;
|
App.Current.DebugSettings.FailFastOnErrors = false;
|
||||||
|
_localKeyboardListener.Dispose();
|
||||||
DisposeAcrylic();
|
DisposeAcrylic();
|
||||||
|
|
||||||
_keyboardListener.Stop();
|
_keyboardListener.Stop();
|
||||||
@ -682,4 +698,10 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
|
|
||||||
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
|
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_localKeyboardListener.Dispose();
|
||||||
|
DisposeAcrylic();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,3 +48,9 @@ DWM_CLOAKED_APP
|
|||||||
CoWaitForMultipleObjects
|
CoWaitForMultipleObjects
|
||||||
INFINITE
|
INFINITE
|
||||||
CWMO_FLAGS
|
CWMO_FLAGS
|
||||||
|
|
||||||
|
GetCurrentThreadId
|
||||||
|
SetWindowsHookEx
|
||||||
|
UnhookWindowsHookEx
|
||||||
|
CallNextHookEx
|
||||||
|
GetModuleHandle
|
@ -8,7 +8,6 @@ using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
|
|||||||
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
|
using Microsoft.CmdPal.Ext.WindowsTerminal.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Microsoft.UI.Xaml.Media.Imaging;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Pages;
|
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Pages;
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ internal sealed partial class ProfilesListPage : ListPage
|
|||||||
{
|
{
|
||||||
private readonly TerminalQuery _terminalQuery = new();
|
private readonly TerminalQuery _terminalQuery = new();
|
||||||
private readonly SettingsManager _terminalSettings;
|
private readonly SettingsManager _terminalSettings;
|
||||||
private readonly Dictionary<string, BitmapImage> _logoCache = [];
|
|
||||||
|
|
||||||
private bool showHiddenProfiles;
|
private bool showHiddenProfiles;
|
||||||
private bool openNewTab;
|
private bool openNewTab;
|
||||||
@ -54,14 +52,6 @@ internal sealed partial class ProfilesListPage : ListPage
|
|||||||
MoreCommands = [
|
MoreCommands = [
|
||||||
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake)),
|
new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake)),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Icon = () => GetLogo(profile.Terminal),
|
|
||||||
// Action = _ =>
|
|
||||||
// {
|
|
||||||
// Launch(profile.Terminal.AppUserModelId, profile.Name);
|
|
||||||
// return true;
|
|
||||||
// },
|
|
||||||
// ContextData = profile,
|
|
||||||
#pragma warning restore SA1108
|
#pragma warning restore SA1108
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -70,17 +60,4 @@ internal sealed partial class ProfilesListPage : ListPage
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override IListItem[] GetItems() => Query().ToArray();
|
public override IListItem[] GetItems() => Query().ToArray();
|
||||||
|
|
||||||
private BitmapImage GetLogo(TerminalPackage terminal)
|
|
||||||
{
|
|
||||||
var aumid = terminal.AppUserModelId;
|
|
||||||
|
|
||||||
if (!_logoCache.TryGetValue(aumid, out var value))
|
|
||||||
{
|
|
||||||
value = terminal.GetLogo();
|
|
||||||
_logoCache.Add(aumid, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.UI.Xaml.Media.Imaging;
|
|
||||||
|
|
||||||
// using Wox.Infrastructure.Image;
|
// using Wox.Infrastructure.Image;
|
||||||
namespace Microsoft.CmdPal.Ext.WindowsTerminal;
|
namespace Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||||
@ -30,23 +29,4 @@ public class TerminalPackage
|
|||||||
SettingsPath = settingsPath;
|
SettingsPath = settingsPath;
|
||||||
LogoPath = logoPath;
|
LogoPath = logoPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitmapImage GetLogo()
|
|
||||||
{
|
|
||||||
var image = new BitmapImage();
|
|
||||||
|
|
||||||
if (File.Exists(LogoPath))
|
|
||||||
{
|
|
||||||
using var fileStream = File.OpenRead(LogoPath);
|
|
||||||
image.SetSource(fileStream.AsRandomAccessStream());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not using wox anymore, TODO: find the right new way to handle this
|
|
||||||
// image.UriSource = new Uri(ImageLoader.ErrorIconPath);
|
|
||||||
Logger.LogError($"Logo file not found: {LogoPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -65,12 +65,32 @@ public partial class ListHelpers
|
|||||||
public static void InPlaceUpdateList<T>(IList<T> original, IEnumerable<T> newContents)
|
public static void InPlaceUpdateList<T>(IList<T> original, IEnumerable<T> newContents)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
|
InPlaceUpdateList(original, newContents, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies the contents of `original` in-place, to match those of
|
||||||
|
/// `newContents`. The canonical use being:
|
||||||
|
/// ```cs
|
||||||
|
/// ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(ItemsToFilter, TextToFilterOn));
|
||||||
|
/// ```
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Any type that can be compared for equality</typeparam>
|
||||||
|
/// <param name="original">Collection to modify</param>
|
||||||
|
/// <param name="newContents">The enumerable which `original` should match</param>
|
||||||
|
/// <param name="removedItems">List of items that were removed from the original collection</param>
|
||||||
|
public static void InPlaceUpdateList<T>(IList<T> original, IEnumerable<T> newContents, out List<T> removedItems)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
removedItems = [];
|
||||||
|
|
||||||
// we're not changing newContents - stash this so we don't re-evaluate it every time
|
// we're not changing newContents - stash this so we don't re-evaluate it every time
|
||||||
var numberOfNew = newContents.Count();
|
var numberOfNew = newContents.Count();
|
||||||
|
|
||||||
// Short circuit - new contents should just be empty
|
// Short circuit - new contents should just be empty
|
||||||
if (numberOfNew == 0)
|
if (numberOfNew == 0)
|
||||||
{
|
{
|
||||||
|
removedItems.AddRange(original);
|
||||||
original.Clear();
|
original.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -92,6 +112,7 @@ public partial class ListHelpers
|
|||||||
for (var k = i; k < j; k++)
|
for (var k = i; k < j; k++)
|
||||||
{
|
{
|
||||||
// This item from the original list was not in the new list. Remove it.
|
// This item from the original list was not in the new list. Remove it.
|
||||||
|
removedItems.Add(original[i]);
|
||||||
original.RemoveAt(i);
|
original.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +141,7 @@ public partial class ListHelpers
|
|||||||
while (original.Count > numberOfNew)
|
while (original.Count > numberOfNew)
|
||||||
{
|
{
|
||||||
// RemoveAtEnd
|
// RemoveAtEnd
|
||||||
|
removedItems.Add(original[original.Count - 1]);
|
||||||
original.RemoveAt(original.Count - 1);
|
original.RemoveAt(original.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
|
||||||
<PackageReference Include="Microsoft.Web.WebView2" />
|
|
||||||
<PackageReference Include="System.Drawing.Common" />
|
<PackageReference Include="System.Drawing.Common" />
|
||||||
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
|
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
|
|
||||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||||
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
|
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
|
||||||
<package id="Microsoft.WindowsAppSDK" version="1.7.250513003" targetFramework="native" />
|
|
||||||
</packages>
|
</packages>
|
@ -24,6 +24,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
public HostsEncoding Encoding { get; set; }
|
public HostsEncoding Encoding { get; set; }
|
||||||
|
|
||||||
|
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||||
|
public bool NoLeadingSpaces { get; set; }
|
||||||
|
|
||||||
public HostsProperties()
|
public HostsProperties()
|
||||||
{
|
{
|
||||||
ShowStartupWarning = true;
|
ShowStartupWarning = true;
|
||||||
@ -31,6 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
LoopbackDuplicates = false;
|
LoopbackDuplicates = false;
|
||||||
AdditionalLinesPosition = HostsAdditionalLinesPosition.Top;
|
AdditionalLinesPosition = HostsAdditionalLinesPosition.Top;
|
||||||
Encoding = HostsEncoding.Utf8;
|
Encoding = HostsEncoding.Utf8;
|
||||||
|
NoLeadingSpaces = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,7 +320,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
|||||||
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
||||||
settingsWindow.Activate();
|
settingsWindow.Activate();
|
||||||
settingsWindow.NavigateToSection(StartupPage);
|
settingsWindow.NavigateToSection(StartupPage);
|
||||||
ShowMessageDialog("The application is running in Debug mode.", "DEBUG");
|
|
||||||
#else
|
#else
|
||||||
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
|
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
|
||||||
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
|
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
|
||||||
@ -329,31 +328,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
private async void ShowMessageDialogAndExit(string content, string title = null)
|
|
||||||
#else
|
|
||||||
private async void ShowMessageDialog(string content, string title = null)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
await ShowDialogAsync(content, title);
|
|
||||||
#if !DEBUG
|
|
||||||
this.Exit();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task<IUICommand> ShowDialogAsync(string content, string title = null)
|
|
||||||
{
|
|
||||||
var dialog = new MessageDialog(content, title ?? string.Empty);
|
|
||||||
var handle = NativeMethods.GetActiveWindow();
|
|
||||||
if (handle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeWithWindow.Initialize(dialog, handle);
|
|
||||||
return dialog.ShowAsync().AsTask<IUICommand>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager()
|
public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager()
|
||||||
{
|
{
|
||||||
return ipcmanager;
|
return ipcmanager;
|
||||||
|
@ -49,6 +49,40 @@
|
|||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style
|
||||||
|
x:Key="OfficeKeyCharPresenterStyle"
|
||||||
|
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
||||||
|
TargetType="local:KeyCharPresenter">
|
||||||
|
<!-- Scale to visually align the height of the Office logo and text -->
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="local:KeyCharPresenter">
|
||||||
|
<Grid Height="{TemplateBinding FontSize}">
|
||||||
|
<Viewbox>
|
||||||
|
<PathIcon Data="M1792 405v1238q0 33-10 62t-28 54-44 41-57 27l-555 159q-23 6-47 6-31 0-58-8t-53-24l-363-205q-20-11-31-29t-12-42q0-35 24-59t60-25h470V458L735 584q-43 15-69 53t-26 83v651q0 41-20 73t-55 53l-167 91q-23 12-46 12-40 0-68-28t-28-68V587q0-51 26-96t71-71L949 81q41-23 89-23 17 0 30 2t30 8l555 153q31 9 56 27t44 42 29 54 10 61zm-128 1238V405q0-22-13-38t-34-23l-273-75-64-18-64-18v1586l401-115q21-6 34-22t13-39z" />
|
||||||
|
</Viewbox>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
x:Key="CopilotKeyCharPresenterStyle"
|
||||||
|
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
||||||
|
TargetType="local:KeyCharPresenter">
|
||||||
|
<!-- Scale to visually align the height of the Copilot logo and text -->
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="local:KeyCharPresenter">
|
||||||
|
<Grid Height="{TemplateBinding FontSize}">
|
||||||
|
<Viewbox>
|
||||||
|
<PathIcon Data="M0 1213q0-60 10-124t27-130 35-129 38-121q18-55 41-119t54-129 70-125 87-106 106-74 129-28h661q59 0 114 17t96 64q30 34 46 72t33 81l22 58q11 29 34 52 23 25 56 31t65 9h4q157 0 238 83t82 240q0 60-10 125t-27 130-35 128-38 121q-18 55-41 119t-54 129-70 125-87 106-106 74-129 28H790q-61 0-107-15t-82-44-61-72-46-98q-11-29-24-60t-35-55q-23-25-51-31t-60-9h-4q-157 0-238-83T0 1213zm598-957q-50 0-93 25t-79 68-67 94-54 108-42 106-31 91q-17 51-35 110t-33 119-26 121-10 114q0 102 43 149t147 47h163q39 0 74-12t64-35 50-53 34-67q19-58 35-115t35-117q35-117 70-232t72-233q23-73 47-147t63-141H598zm452 285q69-29 143-29h281q-18-29-29-59t-21-58-21-54-30-44-46-30-69-11q-32 0-60 9t-48 35q-17 23-31 53t-27 63-23 65-19 60zm-296 867h101q39 0 74-12t66-34 52-52 33-68l58-191 42-140q21-70 43-140 11-36 28-69t43-62h-101q-39 0-74 12t-66 34-52 52-33 68q-15 48-29 96t-29 96q-21 70-41 140t-44 140q-11 36-28 68t-43 62zm814-768q-39 0-74 12t-64 35-50 53-34 68q-56 174-107 347t-106 349q-23 74-47 147t-63 141h427q50 0 93-25t79-68 67-94 54-108 42-106 31-91q16-51 34-110t34-119 26-121 10-114q0-102-43-149t-147-47h-162zm-570 867q-69 29-143 29H564q17 28 29 58t22 58 24 54 32 45 48 30 71 11q31 0 60-8t49-35q15-19 29-50t28-65 24-69 18-58z" />
|
||||||
|
</Viewbox>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
<Style
|
<Style
|
||||||
x:Key="GlyphKeyCharPresenterStyle"
|
x:Key="GlyphKeyCharPresenterStyle"
|
||||||
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
BasedOn="{StaticResource DefaultKeyCharPresenterStyle}"
|
||||||
|
@ -95,9 +95,23 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Content is string)
|
if (Content is string key)
|
||||||
{
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case "Copilot":
|
||||||
|
_keyPresenter.Style = (Style)Application.Current.Resources["CopilotKeyCharPresenterStyle"];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Office":
|
||||||
|
_keyPresenter.Style = (Style)Application.Current.Resources["OfficeKeyCharPresenterStyle"];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
|
_keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,9 @@
|
|||||||
<tkcontrols:SettingsCard x:Uid="Hosts_Toggle_LoopbackDuplicates" HeaderIcon="{ui:FontIcon Glyph=}">
|
<tkcontrols:SettingsCard x:Uid="Hosts_Toggle_LoopbackDuplicates" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.LoopbackDuplicates, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.LoopbackDuplicates, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard x:Uid="Hosts_NoLeadingSpaces" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.NoLeadingSpaces, Mode=TwoWay}" />
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard x:Uid="Hosts_Encoding">
|
<tkcontrols:SettingsCard x:Uid="Hosts_Encoding">
|
||||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.Encoding, Mode=TwoWay}">
|
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.Encoding, Mode=TwoWay}">
|
||||||
<ComboBoxItem x:Uid="Hosts_Encoding_Utf8" />
|
<ComboBoxItem x:Uid="Hosts_Encoding_Utf8" />
|
||||||
|
@ -5127,4 +5127,10 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="KeyBack" xml:space="preserve">
|
<data name="KeyBack" xml:space="preserve">
|
||||||
<value>Back key</value>
|
<value>Back key</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Hosts_NoLeadingSpaces.Header" xml:space="preserve">
|
||||||
|
<value>No leading spaces</value>
|
||||||
|
</data>
|
||||||
|
<data name="Hosts_NoLeadingSpaces.Description" xml:space="preserve">
|
||||||
|
<value>Do not prepend spaces to active lines when saving the hosts file</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -105,6 +105,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool NoLeadingSpaces
|
||||||
|
{
|
||||||
|
get => Settings.Properties.NoLeadingSpaces;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != Settings.Properties.NoLeadingSpaces)
|
||||||
|
{
|
||||||
|
Settings.Properties.NoLeadingSpaces = value;
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int AdditionalLinesPosition
|
public int AdditionalLinesPosition
|
||||||
{
|
{
|
||||||
get => (int)Settings.Properties.AdditionalLinesPosition;
|
get => (int)Settings.Properties.AdditionalLinesPosition;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user