HotkeySettings.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.ComponentModel; 8 using System.Globalization; 9 using System.Runtime.CompilerServices; 10 using System.Text; 11 using System.Text.Json.Serialization; 12 using ManagedCommon; 13 using Microsoft.PowerToys.Settings.UI.Library.Utilities; 14 15 namespace Microsoft.PowerToys.Settings.UI.Library 16 { 17 public record HotkeySettings : ICmdLineRepresentable, INotifyPropertyChanged 18 { 19 private const int VKTAB = 0x09; 20 private bool _hasConflict; 21 private string _conflictDescription; 22 private bool _isSystemConflict; 23 private bool _ignoreConflict; 24 25 public event PropertyChangedEventHandler PropertyChanged; 26 27 protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 28 { 29 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 30 } 31 32 public HotkeySettings() 33 { 34 Win = false; 35 Ctrl = false; 36 Alt = false; 37 Shift = false; 38 Code = 0; 39 40 HasConflict = false; 41 } 42 43 /// <summary> 44 /// Initializes a new instance of the <see cref="HotkeySettings"/> class. 45 /// </summary> 46 /// <param name="win">Should Windows key be used</param> 47 /// <param name="ctrl">Should Ctrl key be used</param> 48 /// <param name="alt">Should Alt key be used</param> 49 /// <param name="shift">Should Shift key be used</param> 50 /// <param name="code">Go to https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes to see list of v-keys</param> 51 public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code) 52 { 53 Win = win; 54 Ctrl = ctrl; 55 Alt = alt; 56 Shift = shift; 57 Code = code; 58 HasConflict = false; 59 } 60 61 [JsonIgnore] 62 public bool IgnoreConflict 63 { 64 get => _ignoreConflict; 65 set 66 { 67 if (_ignoreConflict != value) 68 { 69 _ignoreConflict = value; 70 OnPropertyChanged(); 71 } 72 } 73 } 74 75 [JsonIgnore] 76 public bool HasConflict 77 { 78 get => _hasConflict; 79 set 80 { 81 if (_hasConflict != value) 82 { 83 _hasConflict = value; 84 OnPropertyChanged(); 85 } 86 } 87 } 88 89 [JsonIgnore] 90 public string ConflictDescription 91 { 92 get => _ignoreConflict ? null : _conflictDescription; 93 set 94 { 95 if (_conflictDescription != value) 96 { 97 _conflictDescription = value; 98 OnPropertyChanged(); 99 } 100 } 101 } 102 103 [JsonIgnore] 104 public bool IsSystemConflict 105 { 106 get => _isSystemConflict; 107 set 108 { 109 if (_isSystemConflict != value) 110 { 111 _isSystemConflict = value; 112 OnPropertyChanged(); 113 } 114 } 115 } 116 117 public virtual void UpdateConflictStatus() 118 { 119 Logger.LogInfo($"{this.ToString()}"); 120 } 121 122 [JsonPropertyName("win")] 123 public bool Win { get; set; } 124 125 [JsonPropertyName("ctrl")] 126 public bool Ctrl { get; set; } 127 128 [JsonPropertyName("alt")] 129 public bool Alt { get; set; } 130 131 [JsonPropertyName("shift")] 132 public bool Shift { get; set; } 133 134 [JsonPropertyName("code")] 135 public int Code { get; set; } 136 137 // This is currently needed for FancyZones, we need to unify these two objects 138 // see src\common\settings_objects.h 139 [JsonPropertyName("key")] 140 public string Key { get; set; } = string.Empty; 141 142 public override string ToString() 143 { 144 StringBuilder output = new StringBuilder(); 145 146 if (Win) 147 { 148 output.Append("Win + "); 149 } 150 151 if (Ctrl) 152 { 153 output.Append("Ctrl + "); 154 } 155 156 if (Alt) 157 { 158 output.Append("Alt + "); 159 } 160 161 if (Shift) 162 { 163 output.Append("Shift + "); 164 } 165 166 if (Code > 0) 167 { 168 var localKey = Helper.GetKeyName((uint)Code); 169 output.Append(localKey); 170 } 171 else if (output.Length >= 2) 172 { 173 output.Remove(output.Length - 2, 2); 174 } 175 176 return output.ToString(); 177 } 178 179 public List<object> GetKeysList() 180 { 181 List<object> shortcutList = new List<object>(); 182 183 if (Win) 184 { 185 shortcutList.Add(92); // The Windows key or button. 186 } 187 188 if (Ctrl) 189 { 190 shortcutList.Add("Ctrl"); 191 } 192 193 if (Alt) 194 { 195 shortcutList.Add("Alt"); 196 } 197 198 if (Shift) 199 { 200 shortcutList.Add(16); // The Shift key or button. 201 } 202 203 if (Code > 0) 204 { 205 switch (Code) 206 { 207 // https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348 208 case 38: // The Up Arrow key or button. 209 case 40: // The Down Arrow key or button. 210 case 37: // The Left Arrow key or button. 211 case 39: // The Right Arrow key or button. 212 // case 8: // The Back key or button. 213 // case 13: // The Enter key or button. 214 shortcutList.Add(Code); 215 break; 216 default: 217 var localKey = Helper.GetKeyName((uint)Code); 218 shortcutList.Add(localKey); 219 break; 220 } 221 } 222 223 return shortcutList; 224 } 225 226 public bool IsValid() 227 { 228 if (IsAccessibleShortcut()) 229 { 230 return false; 231 } 232 233 return (Alt || Ctrl || Win || Shift) && Code != 0; 234 } 235 236 public bool IsEmpty() 237 { 238 return !Alt && !Ctrl && !Win && !Shift && Code == 0; 239 } 240 241 public bool IsAccessibleShortcut() 242 { 243 // Shift+Tab and Tab are accessible shortcuts 244 if ((!Alt && !Ctrl && !Win && Shift && Code == VKTAB) 245 || (!Alt && !Ctrl && !Win && !Shift && Code == VKTAB)) 246 { 247 return true; 248 } 249 250 return false; 251 } 252 253 public static bool TryParseFromCmd(string cmd, out object result) 254 { 255 bool win = false, ctrl = false, alt = false, shift = false; 256 int code = 0; 257 258 var parts = cmd.Split('+'); 259 foreach (var part in parts) 260 { 261 switch (part.Trim().ToLower(CultureInfo.InvariantCulture)) 262 { 263 case "win": 264 win = true; 265 break; 266 case "ctrl": 267 ctrl = true; 268 break; 269 case "alt": 270 alt = true; 271 break; 272 case "shift": 273 shift = true; 274 break; 275 default: 276 if (!TryParseKeyCode(part, out code)) 277 { 278 result = null; 279 return false; 280 } 281 282 break; 283 } 284 } 285 286 result = new HotkeySettings(win, ctrl, alt, shift, code); 287 return true; 288 } 289 290 private static bool TryParseKeyCode(string key, out int keyCode) 291 { 292 // ASCII symbol 293 if (key.Length == 1 && char.IsLetterOrDigit(key[0])) 294 { 295 keyCode = char.ToUpper(key[0], CultureInfo.InvariantCulture); 296 return true; 297 } 298 299 // VK code 300 else if (key.Length == 4 && key.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) 301 { 302 return int.TryParse(key.AsSpan(2), NumberStyles.HexNumber, null, out keyCode); 303 } 304 305 // Alias 306 else 307 { 308 keyCode = (int)Utilities.Helper.GetKeyValue(key); 309 return keyCode != 0; 310 } 311 } 312 313 public bool TryToCmdRepresentable(out string result) 314 { 315 result = ToString(); 316 result = result.Replace(" ", null); 317 return true; 318 } 319 } 320 }