diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index bc5ba04289..7e460dba2f 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -637,6 +637,7 @@ hmodule hmonitor homies homljgmgpmcbpjbnjpfijnhipfkiclkd +HOOKPROC HORZRES HORZSIZE Hostbackdropbrush diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md index c1be49a0cd..1709d12abe 100644 --- a/doc/devdocs/readme.md +++ b/doc/devdocs/readme.md @@ -52,7 +52,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an ## Rules - **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. - 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. diff --git a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs index 8eaa37a348..81052fd101 100644 --- a/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs +++ b/src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs @@ -298,5 +298,34 @@ namespace Hosts.Tests var hidden = fileSystem.FileInfo.New(service.HostsFilePath).Attributes.HasFlag(FileAttributes.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(); + 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); + } } } diff --git a/src/modules/Hosts/Hosts/Settings/UserSettings.cs b/src/modules/Hosts/Hosts/Settings/UserSettings.cs index 3530a3f74b..75da5d214d 100644 --- a/src/modules/Hosts/Hosts/Settings/UserSettings.cs +++ b/src/modules/Hosts/Hosts/Settings/UserSettings.cs @@ -26,6 +26,8 @@ namespace Hosts.Settings private bool _loopbackDuplicates; + public bool NoLeadingSpaces { get; private set; } + public bool LoopbackDuplicates { get => _loopbackDuplicates; @@ -88,6 +90,7 @@ namespace Hosts.Settings AdditionalLinesPosition = (HostsAdditionalLinesPosition)settings.Properties.AdditionalLinesPosition; Encoding = (HostsEncoding)settings.Properties.Encoding; LoopbackDuplicates = settings.Properties.LoopbackDuplicates; + NoLeadingSpaces = settings.Properties.NoLeadingSpaces; } retry = false; diff --git a/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs b/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs index b07eb8f93c..83aa3544b1 100644 --- a/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs +++ b/src/modules/Hosts/HostsUILib/Helpers/HostsService.cs @@ -157,7 +157,7 @@ namespace HostsUILib.Helpers { lineBuilder.Append('#').Append(' '); } - else if (anyDisabled) + else if (anyDisabled && !_userSettings.NoLeadingSpaces) { lineBuilder.Append(' ').Append(' '); } diff --git a/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs b/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs index 21a8e6fa36..46c7a7dab5 100644 --- a/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs +++ b/src/modules/Hosts/HostsUILib/Settings/IUserSettings.cs @@ -19,5 +19,7 @@ namespace HostsUILib.Settings event EventHandler LoopbackDuplicatesChanged; public delegate void OpenSettingsFunction(); + + public bool NoLeadingSpaces { get; } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListViewModel.cs index 5a5a14fbdd..10fc9445ad 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ListViewModel.cs @@ -63,6 +63,7 @@ public partial class ListViewModel : PageViewModel, IDisposable private Task? _initializeItemsTask; private CancellationTokenSource? _cancellationTokenSource; + private CancellationTokenSource? _fetchItemsCancellationTokenSource; private ListItemViewModel? _lastSelectedItem; @@ -129,22 +130,38 @@ public partial class ListViewModel : PageViewModel, IDisposable //// Run on background thread, from InitializeAsync or Model_ItemsChanged 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 // see 9806fe5d8 for the last commit that had this with sections _isFetching = true; + // Collect all the items into new viewmodels + Collection newViewModels = []; + try { + // Check for cancellation before starting expensive operations + cancellationToken.ThrowIfCancellationRequested(); + var newItems = _model.Unsafe!.GetItems(); - // Collect all the items into new viewmodels - Collection newViewModels = []; + // Check for cancellation after getting items from extension + cancellationToken.ThrowIfCancellationRequested(); // TODO we can probably further optimize this by also keeping a // HashSet of every ExtensionObject we currently have, and only // building new viewmodels for the ones we haven't already built. foreach (var item in newItems) { + // Check for cancellation during item processing + cancellationToken.ThrowIfCancellationRequested(); + ListItemViewModel viewModel = new(item, new(this)); // 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); foreach (var item in firstTwenty) { + cancellationToken.ThrowIfCancellationRequested(); item?.SafeInitializeProperties(); } // Cancel any ongoing search _cancellationTokenSource?.Cancel(); + // Check for cancellation before updating the list + cancellationToken.ThrowIfCancellationRequested(); + + List removedItems = []; lock (_listLock) { // Now that we have new ViewModels for everything from the // 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 // 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) { // 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?.Dispose(); _cancellationTokenSource = null; + + _fetchItemsCancellationTokenSource?.Cancel(); + _fetchItemsCancellationTokenSource?.Dispose(); + _fetchItemsCancellationTokenSource = null; } protected override void UnsafeCleanup() @@ -570,6 +623,7 @@ public partial class ListViewModel : PageViewModel, IDisposable EmptyContent = new(new(null), PageContext); // necessary? _cancellationTokenSource?.Cancel(); + _fetchItemsCancellationTokenSource?.Cancel(); lock (_listLock) { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml index 107db49939..49fef61ecb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml @@ -47,7 +47,8 @@ + Opened="ContextMenuFlyout_Opened" + ShouldConstrainToRootBounds="False"> diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs new file mode 100644 index 0000000000..c3dd7c28bf --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListener.cs @@ -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; + +/// +/// A class that listens for local keyboard events using a Windows hook. +/// +internal sealed class LocalKeyboardListener : IDisposable +{ + /// + /// Event that is raised when a key is pressed down. + /// + public event EventHandler? KeyPressed; + + private bool _disposed; + private UnhookWindowsHookExSafeHandle? _handle; + private HOOKPROC? _hookProc; // Keep reference to prevent GC collection + + /// + /// Registers a global keyboard hook to listen for key down events. + /// + /// + /// Throws if the hook could not be registered, which may happen if the system is unable to set the hook. + /// + 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."); + } + } + + /// + /// Attempts to register a global keyboard hook to listen for key down events. + /// + /// + /// if the keyboard hook was successfully registered; otherwise, . + /// + 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; + } + } +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListenerKeyPressedEventArgs.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListenerKeyPressedEventArgs.cs new file mode 100644 index 0000000000..ce26cd037f --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Helpers/LocalKeyboardListenerKeyPressedEventArgs.cs @@ -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; +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index 012ec3ccf6..5311f4b8b4 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -27,6 +27,7 @@ using Microsoft.Windows.AppLifecycle; using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Graphics; +using Windows.System; using Windows.UI; using Windows.UI.WindowManagement; using Windows.Win32; @@ -44,7 +45,8 @@ public sealed partial class MainWindow : WindowEx, IRecipient, IRecipient, IRecipient, - IRecipient + IRecipient, + 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", "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 List _hotkeys = []; private readonly KeyboardListener _keyboardListener; + private readonly LocalKeyboardListener _localKeyboardListener; private bool _ignoreHotKeyWhenFullScreen = true; private DesktopAcrylicController? _acrylicController; @@ -116,6 +119,18 @@ public sealed partial class MainWindow : WindowEx, { 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(); @@ -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). // Workaround by turning it off before shutdown. App.Current.DebugSettings.FailFastOnErrors = false; + _localKeyboardListener.Dispose(); DisposeAcrylic(); _keyboardListener.Stop(); @@ -682,4 +698,10 @@ public sealed partial class MainWindow : WindowEx, return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam); } + + public void Dispose() + { + _localKeyboardListener.Dispose(); + DisposeAcrylic(); + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/NativeMethods.txt b/src/modules/cmdpal/Microsoft.CmdPal.UI/NativeMethods.txt index a432d9a808..aa31c5a3f2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/NativeMethods.txt +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/NativeMethods.txt @@ -47,4 +47,10 @@ DWM_CLOAKED_APP CoWaitForMultipleObjects INFINITE -CWMO_FLAGS \ No newline at end of file +CWMO_FLAGS + +GetCurrentThreadId +SetWindowsHookEx +UnhookWindowsHookEx +CallNextHookEx +GetModuleHandle \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs index b426e96914..7e9d2cec31 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs @@ -8,7 +8,6 @@ using Microsoft.CmdPal.Ext.WindowsTerminal.Helpers; using Microsoft.CmdPal.Ext.WindowsTerminal.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; -using Microsoft.UI.Xaml.Media.Imaging; namespace Microsoft.CmdPal.Ext.WindowsTerminal.Pages; @@ -16,7 +15,6 @@ internal sealed partial class ProfilesListPage : ListPage { private readonly TerminalQuery _terminalQuery = new(); private readonly SettingsManager _terminalSettings; - private readonly Dictionary _logoCache = []; private bool showHiddenProfiles; private bool openNewTab; @@ -54,14 +52,6 @@ internal sealed partial class ProfilesListPage : ListPage MoreCommands = [ 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 }); } @@ -70,17 +60,4 @@ internal sealed partial class ProfilesListPage : ListPage } 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; - } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/TerminalPackage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/TerminalPackage.cs index 1b2cec7e3d..3301028da1 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/TerminalPackage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/TerminalPackage.cs @@ -5,7 +5,6 @@ using System; using System.IO; using ManagedCommon; -using Microsoft.UI.Xaml.Media.Imaging; // using Wox.Infrastructure.Image; namespace Microsoft.CmdPal.Ext.WindowsTerminal; @@ -30,23 +29,4 @@ public class TerminalPackage SettingsPath = settingsPath; 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; - } } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListHelpers.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListHelpers.cs index 4f004ae78e..39f7c087b0 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListHelpers.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListHelpers.cs @@ -65,12 +65,32 @@ public partial class ListHelpers public static void InPlaceUpdateList(IList original, IEnumerable newContents) where T : class { + InPlaceUpdateList(original, newContents, out _); + } + + /// + /// Modifies the contents of `original` in-place, to match those of + /// `newContents`. The canonical use being: + /// ```cs + /// ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(ItemsToFilter, TextToFilterOn)); + /// ``` + /// + /// Any type that can be compared for equality + /// Collection to modify + /// The enumerable which `original` should match + /// List of items that were removed from the original collection + public static void InPlaceUpdateList(IList original, IEnumerable newContents, out List removedItems) + where T : class + { + removedItems = []; + // we're not changing newContents - stash this so we don't re-evaluate it every time var numberOfNew = newContents.Count(); // Short circuit - new contents should just be empty if (numberOfNew == 0) { + removedItems.AddRange(original); original.Clear(); return; } @@ -92,6 +112,7 @@ public partial class ListHelpers for (var k = i; k < j; k++) { // This item from the original list was not in the new list. Remove it. + removedItems.Add(original[i]); original.RemoveAt(i); } @@ -120,6 +141,7 @@ public partial class ListHelpers while (original.Count > numberOfNew) { // RemoveAtEnd + removedItems.Add(original[original.Count - 1]); original.RemoveAt(original.Count - 1); } diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj index 6217cd25b6..f5f8f2ccbc 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/Microsoft.CommandPalette.Extensions.Toolkit.csproj @@ -41,8 +41,6 @@ all runtime; build; native; contentfiles; analyzers - - diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj index e620524c77..983c1594a4 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.vcxproj @@ -180,4 +180,4 @@ - + \ No newline at end of file diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config index fc2bc5a5df..6a99b79e23 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/packages.config @@ -1,7 +1,5 @@  - - \ No newline at end of file diff --git a/src/settings-ui/Settings.UI.Library/HostsProperties.cs b/src/settings-ui/Settings.UI.Library/HostsProperties.cs index 90a576601d..6ec9924049 100644 --- a/src/settings-ui/Settings.UI.Library/HostsProperties.cs +++ b/src/settings-ui/Settings.UI.Library/HostsProperties.cs @@ -24,6 +24,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library public HostsEncoding Encoding { get; set; } + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool NoLeadingSpaces { get; set; } + public HostsProperties() { ShowStartupWarning = true; @@ -31,6 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library LoopbackDuplicates = false; AdditionalLinesPosition = HostsAdditionalLinesPosition.Top; Encoding = HostsEncoding.Utf8; + NoLeadingSpaces = false; } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs index f897f4bc8c..f3649555fd 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs @@ -320,7 +320,6 @@ namespace Microsoft.PowerToys.Settings.UI WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); settingsWindow.Activate(); settingsWindow.NavigateToSection(StartupPage); - ShowMessageDialog("The application is running in Debug mode.", "DEBUG"); #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. */ 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 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(); - } - public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager() { return ipcmanager; diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml index c45f1ba0d2..a28874b4ee 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/KeyVisual/KeyCharPresenter.xaml @@ -49,6 +49,40 @@ + +