/ src / settings-ui / Settings.UI.Library / HotkeySettings.cs
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  }