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.Globalization;
  6  using System.Text;
  7  using System.Text.Json.Serialization;
  8  
  9  namespace Microsoft.CmdPal.UI.ViewModels.Settings;
 10  
 11  public record HotkeySettings// : ICmdLineRepresentable
 12  {
 13      private const int VKTAB = 0x09;
 14  
 15      public HotkeySettings()
 16      {
 17          Win = false;
 18          Ctrl = false;
 19          Alt = false;
 20          Shift = false;
 21          Code = 0;
 22      }
 23  
 24      /// <summary>
 25      /// Initializes a new instance of the <see cref="HotkeySettings"/> class.
 26      /// </summary>
 27      /// <param name="win">Should Windows key be used</param>
 28      /// <param name="ctrl">Should Ctrl key be used</param>
 29      /// <param name="alt">Should Alt key be used</param>
 30      /// <param name="shift">Should Shift key be used</param>
 31      /// <param name="code">Go to https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes to see list of v-keys</param>
 32      public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code)
 33      {
 34          Win = win;
 35          Ctrl = ctrl;
 36          Alt = alt;
 37          Shift = shift;
 38          Code = code;
 39      }
 40  
 41      [JsonPropertyName("win")]
 42      public bool Win { get; set; }
 43  
 44      [JsonPropertyName("ctrl")]
 45      public bool Ctrl { get; set; }
 46  
 47      [JsonPropertyName("alt")]
 48      public bool Alt { get; set; }
 49  
 50      [JsonPropertyName("shift")]
 51      public bool Shift { get; set; }
 52  
 53      [JsonPropertyName("code")]
 54      public int Code { get; set; }
 55  
 56      // This is currently needed for FancyZones, we need to unify these two objects
 57      // see src\common\settings_objects.h
 58      [JsonPropertyName("key")]
 59      public string Key { get; set; } = string.Empty;
 60  
 61      public override string ToString()
 62      {
 63          var output = new StringBuilder();
 64  
 65          if (Win)
 66          {
 67              output.Append("Win + ");
 68          }
 69  
 70          if (Ctrl)
 71          {
 72              output.Append("Ctrl + ");
 73          }
 74  
 75          if (Alt)
 76          {
 77              output.Append("Alt + ");
 78          }
 79  
 80          if (Shift)
 81          {
 82              output.Append("Shift + ");
 83          }
 84  
 85          if (Code > 0)
 86          {
 87              var localKey = Helper.GetKeyName((uint)Code);
 88              output.Append(localKey);
 89          }
 90          else if (output.Length >= 2)
 91          {
 92              output.Remove(output.Length - 2, 2);
 93          }
 94  
 95          return output.ToString();
 96      }
 97  
 98      public List<object> GetKeysList()
 99      {
100          var shortcutList = new List<object>();
101  
102          if (Win)
103          {
104              shortcutList.Add(92); // The Windows key or button.
105          }
106  
107          if (Ctrl)
108          {
109              shortcutList.Add("Ctrl");
110          }
111  
112          if (Alt)
113          {
114              shortcutList.Add("Alt");
115          }
116  
117          if (Shift)
118          {
119              shortcutList.Add("Shift");
120  
121              // shortcutList.Add(16); // The Shift key or button.
122          }
123  
124          if (Code > 0)
125          {
126              switch (Code)
127              {
128                  // https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348
129                  case 38: // The Up Arrow key or button.
130                  case 40: // The Down Arrow key or button.
131                  case 37: // The Left Arrow key or button.
132                  case 39: // The Right Arrow key or button.
133                           // case 8: // The Back key or button.
134                           // case 13: // The Enter key or button.
135                      shortcutList.Add(Code);
136                      break;
137                  default:
138                      var localKey = Helper.GetKeyName((uint)Code);
139                      shortcutList.Add(localKey);
140                      break;
141              }
142          }
143  
144          return shortcutList;
145      }
146  
147      public bool IsValid()
148      {
149          return IsAccessibleShortcut() ? false : (Alt || Ctrl || Win || Shift) && Code != 0;
150      }
151  
152      public bool IsEmpty()
153      {
154          return !Alt && !Ctrl && !Win && !Shift && Code == 0;
155      }
156  
157      public bool IsAccessibleShortcut()
158      {
159          // Shift+Tab and Tab are accessible shortcuts
160          return (!Alt && !Ctrl && !Win && Shift && Code == VKTAB)
161              || (!Alt && !Ctrl && !Win && !Shift && Code == VKTAB);
162      }
163  
164      public static bool TryParseFromCmd(string cmd, out object? result)
165      {
166          bool win = false, ctrl = false, alt = false, shift = false;
167          var code = 0;
168  
169          var parts = cmd.Split('+');
170          foreach (var part in parts)
171          {
172              switch (part.Trim().ToLower(CultureInfo.InvariantCulture))
173              {
174                  case "win":
175                      win = true;
176                      break;
177                  case "ctrl":
178                      ctrl = true;
179                      break;
180                  case "alt":
181                      alt = true;
182                      break;
183                  case "shift":
184                      shift = true;
185                      break;
186                  default:
187                      if (!TryParseKeyCode(part, out code))
188                      {
189                          result = null;
190                          return false;
191                      }
192  
193                      break;
194              }
195          }
196  
197          result = new HotkeySettings(win, ctrl, alt, shift, code);
198          return true;
199      }
200  
201      private static bool TryParseKeyCode(string key, out int keyCode)
202      {
203          // ASCII symbol
204          if (key.Length == 1 && char.IsLetterOrDigit(key[0]))
205          {
206              keyCode = char.ToUpper(key[0], CultureInfo.InvariantCulture);
207              return true;
208          }
209  
210          // VK code
211          else if (key.Length == 4 && key.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
212          {
213              return int.TryParse(key.AsSpan(2), NumberStyles.HexNumber, null, out keyCode);
214          }
215  
216          // Alias
217          else
218          {
219              keyCode = (int)Helper.GetKeyValue(key);
220              return keyCode != 0;
221          }
222      }
223  
224      public bool TryToCmdRepresentable(out string result)
225      {
226          result = ToString();
227          result = result.Replace(" ", null);
228          return true;
229      }
230  }