mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 20:20:38 +01:00
This upgrades to [v0.0.24](https://github.com/check-spelling/check-spelling/releases/tag/v0.0.24). A number of GitHub APIs are being turned off shortly, so you need to upgrade or various uncertain outcomes will occur. There's a new accessibility forbidden pattern: > Do not use `(click) here` links > For more information, see: > * https://www.w3.org/QA/Tips/noClickHere > * https://webaim.org/techniques/hypertext/link_text > * https://granicus.com/blog/why-click-here-links-are-bad/ > * https://heyoka.medium.com/dont-use-click-here-f32f445d1021 ```pl (?i)(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:</|\]\() ``` There are some minor bugs that I'm aware of and which I've fixed since this release, but I don't expect to make another release this month. I've added a pair of patterns for includes and pragmas. My argument is that the **compiler** will _generally_ tell you if you've misspelled an include and the **linker** will _generally_ tell you if you misspell a lib. - There's a caveat here: If your include case-insensitively matches the referenced file (but doesn't properly match it), then unless you either use a case-sensitive file system (as opposed to case-preserving) or beg clang to warn, you won't notice when you make this specific mistake -- this matters in that a couple of Windows headers (e.g. Unknwn.h) have particular case and repositories don't tend to consistently/properly write them.
320 lines
10 KiB
C#
320 lines
10 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.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using System.Threading;
|
|
using System.Windows.Input;
|
|
|
|
using ManagedCommon;
|
|
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
|
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
|
|
|
|
namespace Microsoft.PowerToys.Settings.UI.Library
|
|
{
|
|
public class KeysDataModel : INotifyPropertyChanged
|
|
{
|
|
[JsonPropertyName("originalKeys")]
|
|
public string OriginalKeys { get; set; }
|
|
|
|
[JsonPropertyName("secondKeyOfChord")]
|
|
public uint SecondKeyOfChord { get; set; }
|
|
|
|
[JsonPropertyName("newRemapKeys")]
|
|
public string NewRemapKeys { get; set; }
|
|
|
|
[JsonPropertyName("unicodeText")]
|
|
public string NewRemapString { get; set; }
|
|
|
|
[JsonPropertyName("runProgramFilePath")]
|
|
public string RunProgramFilePath { get; set; }
|
|
|
|
[JsonPropertyName("runProgramArgs")]
|
|
public string RunProgramArgs { get; set; }
|
|
|
|
[JsonPropertyName("openUri")]
|
|
public string OpenUri { get; set; }
|
|
|
|
[JsonPropertyName("operationType")]
|
|
public int OperationType { get; set; }
|
|
|
|
private enum KeyboardManagerEditorType
|
|
{
|
|
KeyEditor = 0,
|
|
ShortcutEditor,
|
|
}
|
|
|
|
public const string CommaSeparator = "<comma>";
|
|
|
|
private static Process editor;
|
|
private ICommand _editShortcutItemCommand;
|
|
|
|
public ICommand EditShortcutItem => _editShortcutItemCommand ?? (_editShortcutItemCommand = new RelayCommand<object>(OnEditShortcutItem));
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
protected virtual void OnPropertyChanged(string propertyName)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
public void OnEditShortcutItem(object parameter)
|
|
{
|
|
OpenEditor((int)KeyboardManagerEditorType.ShortcutEditor);
|
|
}
|
|
|
|
private async void OpenEditor(int type)
|
|
{
|
|
if (editor != null)
|
|
{
|
|
BringProcessToFront(editor);
|
|
return;
|
|
}
|
|
|
|
const string PowerToyName = KeyboardManagerSettings.ModuleName;
|
|
const string KeyboardManagerEditorPath = "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe";
|
|
try
|
|
{
|
|
if (editor != null && editor.HasExited)
|
|
{
|
|
Logger.LogInfo($"Previous instance of {PowerToyName} editor exited");
|
|
editor = null;
|
|
}
|
|
|
|
if (editor != null)
|
|
{
|
|
Logger.LogInfo($"The {PowerToyName} editor instance {editor.Id} exists. Bringing the process to the front");
|
|
BringProcessToFront(editor);
|
|
return;
|
|
}
|
|
|
|
string path = Path.Combine(Environment.CurrentDirectory, KeyboardManagerEditorPath);
|
|
Logger.LogInfo($"Starting {PowerToyName} editor from {path}");
|
|
|
|
// InvariantCulture: type represents the KeyboardManagerEditorType enum value
|
|
editor = Process.Start(path, $"{type.ToString(CultureInfo.InvariantCulture)} {Environment.ProcessId}");
|
|
|
|
await editor.WaitForExitAsync();
|
|
|
|
editor = null;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
editor = null;
|
|
Logger.LogError($"Exception encountered when opening an {PowerToyName} editor", e);
|
|
}
|
|
}
|
|
|
|
private static void BringProcessToFront(Process process)
|
|
{
|
|
if (process == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IntPtr handle = process.MainWindowHandle;
|
|
if (NativeMethods.IsIconic(handle))
|
|
{
|
|
NativeMethods.ShowWindow(handle, NativeMethods.SWRESTORE);
|
|
}
|
|
|
|
NativeMethods.SetForegroundWindow(handle);
|
|
}
|
|
|
|
private static List<string> MapKeysOnlyChord(uint secondKeyOfChord)
|
|
{
|
|
var result = new List<string>();
|
|
if (secondKeyOfChord <= 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
result.Add(Helper.GetKeyName(secondKeyOfChord));
|
|
|
|
return result;
|
|
}
|
|
|
|
private static List<string> MapKeys(string stringOfKeys, uint secondKeyOfChord, bool splitChordsWithComma = false)
|
|
{
|
|
if (stringOfKeys == null)
|
|
{
|
|
return new List<string>();
|
|
}
|
|
|
|
if (secondKeyOfChord > 0)
|
|
{
|
|
var keys = stringOfKeys.Split(';');
|
|
return keys.Take(keys.Length - 1)
|
|
.Select(uint.Parse)
|
|
.Select(Helper.GetKeyName)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
if (splitChordsWithComma)
|
|
{
|
|
var keys = stringOfKeys.Split(';')
|
|
.Select(uint.Parse)
|
|
.Select(Helper.GetKeyName)
|
|
.ToList();
|
|
keys.Insert(keys.Count - 1, CommaSeparator);
|
|
return keys;
|
|
}
|
|
else
|
|
{
|
|
return stringOfKeys
|
|
.Split(';')
|
|
.Select(uint.Parse)
|
|
.Select(Helper.GetKeyName)
|
|
.ToList();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static List<string> MapKeys(string stringOfKeys)
|
|
{
|
|
return MapKeys(stringOfKeys, 0);
|
|
}
|
|
|
|
public List<string> GetMappedOriginalKeys(bool ignoreSecondKeyInChord, bool splitChordsWithComma = false)
|
|
{
|
|
if (ignoreSecondKeyInChord && SecondKeyOfChord > 0)
|
|
{
|
|
return MapKeys(OriginalKeys, SecondKeyOfChord);
|
|
}
|
|
else
|
|
{
|
|
return MapKeys(OriginalKeys, 0, splitChordsWithComma);
|
|
}
|
|
}
|
|
|
|
public List<string> GetMappedOriginalKeysOnlyChord()
|
|
{
|
|
return MapKeysOnlyChord(SecondKeyOfChord);
|
|
}
|
|
|
|
public List<string> GetMappedOriginalKeys()
|
|
{
|
|
return GetMappedOriginalKeys(false);
|
|
}
|
|
|
|
public List<string> GetMappedOriginalKeysWithSplitChord()
|
|
{
|
|
return GetMappedOriginalKeys(false, true);
|
|
}
|
|
|
|
public bool IsRunProgram
|
|
{
|
|
get
|
|
{
|
|
return OperationType == 1;
|
|
}
|
|
}
|
|
|
|
public bool IsOpenURI
|
|
{
|
|
get
|
|
{
|
|
return OperationType == 2;
|
|
}
|
|
}
|
|
|
|
public bool IsOpenUriOrIsRunProgram
|
|
{
|
|
get
|
|
{
|
|
return IsOpenURI || IsRunProgram;
|
|
}
|
|
}
|
|
|
|
public bool HasChord
|
|
{
|
|
get
|
|
{
|
|
return SecondKeyOfChord > 0;
|
|
}
|
|
}
|
|
|
|
public List<string> GetMappedNewRemapKeys(int runProgramMaxLength)
|
|
{
|
|
if (IsRunProgram)
|
|
{
|
|
// we're going to just pretend this is a "key" if we have a RunProgramFilePath
|
|
if (string.IsNullOrEmpty(RunProgramFilePath))
|
|
{
|
|
return new List<string>();
|
|
}
|
|
else
|
|
{
|
|
return new List<string> { FormatFakeKeyForDisplay(runProgramMaxLength) };
|
|
}
|
|
}
|
|
else if (IsOpenURI)
|
|
{
|
|
// we're going to just pretend this is a "key" if we have a RunProgramFilePath
|
|
if (string.IsNullOrEmpty(OpenUri))
|
|
{
|
|
return new List<string>();
|
|
}
|
|
else
|
|
{
|
|
if (OpenUri.Length > runProgramMaxLength)
|
|
{
|
|
return new List<string> { $"{OpenUri.Substring(0, runProgramMaxLength - 3)}..." };
|
|
}
|
|
else
|
|
{
|
|
return new List<string> { OpenUri };
|
|
}
|
|
}
|
|
}
|
|
|
|
return (string.IsNullOrEmpty(NewRemapString) || NewRemapString == "*Unsupported*") ? MapKeys(NewRemapKeys) : new List<string> { NewRemapString };
|
|
}
|
|
|
|
// Instead of doing something fancy pants, we'll just display the RunProgramFilePath data when it's IsRunProgram
|
|
// It truncates the start of the program to run, if it's long and truncates the end of the args if it's long
|
|
// e.g.: c:\MyCool\PathIs\Long\software.exe myArg1 myArg2 myArg3 -> (something like) "...ng\software.exe myArg1..."
|
|
// the idea is you get the most important part of the program to run and some of the args in case that the only thing thats different,
|
|
// e.g: "...path\software.exe cool1.txt" and "...path\software.exe cool3.txt"
|
|
private string FormatFakeKeyForDisplay(int runProgramMaxLength)
|
|
{
|
|
// was going to use this:
|
|
var fakeKey = Environment.ExpandEnvironmentVariables(RunProgramFilePath);
|
|
try
|
|
{
|
|
if (File.Exists(fakeKey))
|
|
{
|
|
fakeKey = Path.GetFileName(Environment.ExpandEnvironmentVariables(RunProgramFilePath));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
|
|
fakeKey = $"{fakeKey} {RunProgramArgs}".Trim();
|
|
|
|
if (fakeKey.Length > runProgramMaxLength)
|
|
{
|
|
fakeKey = $"{fakeKey.Substring(0, runProgramMaxLength - 3)}...";
|
|
}
|
|
|
|
return fakeKey;
|
|
}
|
|
|
|
public string ToJsonString()
|
|
{
|
|
return JsonSerializer.Serialize(this);
|
|
}
|
|
}
|
|
}
|