/ src / settings-ui / Settings.UI / ViewModels / MouseUtilsViewModel_MouseJump.cs
MouseUtilsViewModel_MouseJump.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.Drawing;
  9  using System.Drawing.Imaging;
 10  using System.IO;
 11  using System.Linq;
 12  using System.Reflection;
 13  using System.Runtime.CompilerServices;
 14  using global::PowerToys.GPOWrapper;
 15  using Microsoft.PowerToys.Settings.UI.Library;
 16  using Microsoft.PowerToys.Settings.UI.Library.Helpers;
 17  using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
 18  using Microsoft.UI.Xaml.Media;
 19  using Microsoft.UI.Xaml.Media.Imaging;
 20  using MouseJump.Common.Helpers;
 21  using MouseJump.Common.Imaging;
 22  using MouseJump.Common.Models.Drawing;
 23  using MouseJump.Common.Models.Settings;
 24  using MouseJump.Common.Models.Styles;
 25  
 26  namespace Microsoft.PowerToys.Settings.UI.ViewModels
 27  {
 28      public partial class MouseUtilsViewModel : PageViewModelBase
 29      {
 30          private GpoRuleConfigured _jumpEnabledGpoRuleConfiguration;
 31          private bool _jumpEnabledStateIsGPOConfigured;
 32          private bool _isMouseJumpEnabled;
 33  
 34          internal MouseJumpSettings MouseJumpSettingsConfig { get; set; }
 35  
 36          private void InitializeMouseJumpSettings(ISettingsRepository<MouseJumpSettings> mouseJumpSettingsRepository)
 37          {
 38              ArgumentNullException.ThrowIfNull(mouseJumpSettingsRepository);
 39              this.MouseJumpSettingsConfig = mouseJumpSettingsRepository.SettingsConfig;
 40  
 41              this.MouseJumpSettingsConfig.Properties.ThumbnailSize.PropertyChanged += this.MouseJumpThumbnailSizePropertyChanged;
 42          }
 43  
 44          private void InitializeMouseJumpEnabledValues()
 45          {
 46              _jumpEnabledGpoRuleConfiguration = GPOWrapper.GetConfiguredMouseJumpEnabledValue();
 47              if (_jumpEnabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _jumpEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
 48              {
 49                  // Get the enabled state from GPO.
 50                  _jumpEnabledStateIsGPOConfigured = true;
 51                  _isMouseJumpEnabled = _jumpEnabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
 52              }
 53              else
 54              {
 55                  _isMouseJumpEnabled = GeneralSettingsConfig.Enabled.MouseJump;
 56              }
 57          }
 58  
 59          public bool IsMouseJumpEnabled
 60          {
 61              get => _isMouseJumpEnabled;
 62              set
 63              {
 64                  if (_jumpEnabledStateIsGPOConfigured)
 65                  {
 66                      // If it's GPO configured, shouldn't be able to change this state.
 67                      return;
 68                  }
 69  
 70                  if (_isMouseJumpEnabled != value)
 71                  {
 72                      _isMouseJumpEnabled = value;
 73  
 74                      GeneralSettingsConfig.Enabled.MouseJump = value;
 75                      OnPropertyChanged(nameof(_isMouseJumpEnabled));
 76  
 77                      OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
 78                      SendConfigMSG(outgoing.ToString());
 79  
 80                      NotifyMouseJumpPropertyChanged();
 81                  }
 82              }
 83          }
 84  
 85          public bool IsJumpEnabledGpoConfigured
 86          {
 87              get => _jumpEnabledStateIsGPOConfigured;
 88          }
 89  
 90          public HotkeySettings MouseJumpActivationShortcut
 91          {
 92              get
 93              {
 94                  return MouseJumpSettingsConfig.Properties.ActivationShortcut;
 95              }
 96  
 97              set
 98              {
 99                  if (MouseJumpSettingsConfig.Properties.ActivationShortcut != value)
100                  {
101                      MouseJumpSettingsConfig.Properties.ActivationShortcut = value ?? MouseJumpSettingsConfig.Properties.DefaultActivationShortcut;
102                      NotifyMouseJumpPropertyChanged();
103                  }
104              }
105          }
106  
107          public MouseJumpThumbnailSize MouseJumpThumbnailSize
108          {
109              get
110              {
111                  return MouseJumpSettingsConfig.Properties.ThumbnailSize;
112              }
113  
114              set
115              {
116                  if ((MouseJumpSettingsConfig.Properties.ThumbnailSize.Width != value?.Width)
117                      && (MouseJumpSettingsConfig.Properties.ThumbnailSize.Height != value?.Height))
118                  {
119                      MouseJumpSettingsConfig.Properties.ThumbnailSize = value;
120                      NotifyMouseJumpPropertyChanged();
121                  }
122              }
123          }
124  
125          private static Bitmap LoadImageResource(string filename)
126          {
127              var assembly = Assembly.GetExecutingAssembly();
128              var assemblyName = new AssemblyName(assembly.FullName ?? throw new InvalidOperationException());
129  
130              // Build the fully-qualified manifest resource name. Historically, subtle casing differences
131              // (e.g. folder names or the assembly name) caused exact (case-sensitive) lookup failures on
132              // some developer machines when the embedded resource's actual name differed only by case.
133              // Manifest resource name comparison here does not need to be case-sensitive, so we resolve
134              // the actual name using an OrdinalIgnoreCase match, then use the real casing for the stream.
135              var resourceName = $"Microsoft.{assemblyName.Name}.{filename.Replace("/", ".")}";
136              var resourceNames = assembly.GetManifestResourceNames();
137              var actualResourceName = resourceNames.FirstOrDefault(n => string.Equals(n, resourceName, StringComparison.OrdinalIgnoreCase));
138              if (actualResourceName is null)
139              {
140                  throw new InvalidOperationException($"Embedded resource '{resourceName}' (case-insensitive) does not exist.");
141              }
142  
143              var stream = assembly.GetManifestResourceStream(actualResourceName)
144                  ?? throw new InvalidOperationException();
145              var image = (Bitmap)Image.FromStream(stream);
146              return image;
147          }
148  
149          private static Lazy<Bitmap> MouseJumpDesktopImage => new(
150              () => MouseUtilsViewModel.LoadImageResource("UI/Images/MouseJump-Desktop.png")
151          );
152  
153          public ImageSource MouseJumpPreviewImage
154          {
155              get
156              {
157                  // keep these in sync with the layout of "Images\MouseJump-Desktop.png"
158                  var screens = new List<RectangleInfo>()
159                  {
160                      /*
161                          these magic numbers are the pixel dimensions of the individual screens on the
162                          fake desktop image - "Images\MouseJump-Desktop.png" - used to generate the
163                          preview image in the Settings UI properties page for Mouse Jump. if you update
164                          the fake desktop image be sure to update these values as well.
165                      */
166                      new(635, 172, 272, 168),
167                      new(0, 0, 635, 339),
168                  };
169                  var desktopSize = LayoutHelper.GetCombinedScreenBounds(screens).Size;
170                  /*
171                      magic number 283 is the content height left in the settings card after removing the top and bottom chrome:
172  
173                          300px settings card height - 1px top border - 7px top margin - 8px bottom margin - 1px bottom border = 283px image height
174  
175                      this ensures we get a preview image scaled at 100% so borders, etc., are shown at exact pixel sizes in the preview
176                  */
177                  var canvasSize = new SizeInfo(desktopSize.Width, 283).Clamp(desktopSize);
178  
179                  var previewType = Enum.TryParse<PreviewType>(this.MouseJumpPreviewType, true, out var previewTypeResult)
180                      ? previewTypeResult
181                      : PreviewType.Bezelled;
182                  var previewStyle = previewType switch
183                  {
184                      PreviewType.Compact => StyleHelper.CompactPreviewStyle.WithCanvasSize(desktopSize),
185                      PreviewType.Bezelled => StyleHelper.BezelledPreviewStyle.WithCanvasSize(desktopSize),
186                      PreviewType.Custom => new PreviewStyle(
187                          canvasSize: canvasSize,
188                          canvasStyle: new(
189                              marginStyle: new(0),
190                              borderStyle: new(
191                                  color: ConfigHelper.DeserializeFromConfigColorString(
192                                      this.MouseJumpBorderColor),
193                                  all: this.MouseJumpBorderThickness,
194                                  depth: this.MouseJumpBorder3dDepth
195                              ),
196                              paddingStyle: new(
197                                  all: this.MouseJumpBorderPadding
198                              ),
199                              backgroundStyle: new(
200                                  color1: ConfigHelper.DeserializeFromConfigColorString(
201                                      this.MouseJumpBackgroundColor1),
202                                  color2: ConfigHelper.DeserializeFromConfigColorString(
203                                      this.MouseJumpBackgroundColor2)
204                              )
205                          ),
206                          screenStyle: new(
207                              marginStyle: new(
208                                  all: this.MouseJumpScreenMargin
209                              ),
210                              borderStyle: new(
211                                  color: ConfigHelper.DeserializeFromConfigColorString(
212                                      this.MouseJumpBezelColor),
213                                  all: this.MouseJumpBezelThickness,
214                                  depth: this.MouseJumpBezel3dDepth
215                              ),
216                              paddingStyle: new(0),
217                              backgroundStyle: new(
218                                  color1: ConfigHelper.DeserializeFromConfigColorString(
219                                      this.MouseJumpScreenColor1),
220                                  color2: ConfigHelper.DeserializeFromConfigColorString(
221                                      this.MouseJumpScreenColor2)
222                              )
223                          )),
224                      _ => throw new InvalidOperationException(
225                          $"Unhandled {nameof(MouseJumpPreviewType)} '{previewType}'"),
226                  };
227  
228                  var previewLayout = LayoutHelper.GetPreviewLayout(
229                      previewStyle: previewStyle,
230                      screens: screens,
231                      activatedLocation: new(0, 0));
232  
233                  var desktopImage = MouseUtilsViewModel.MouseJumpDesktopImage.Value;
234                  var imageCopyService = new StaticImageRegionCopyService(desktopImage);
235                  using var previewImage = DrawingHelper.RenderPreview(
236                      previewLayout,
237                      imageCopyService);
238  
239                  // save the image to a memory stream
240                  using var stream = new MemoryStream();
241                  previewImage.Save(stream, ImageFormat.Png);
242                  stream.Position = 0;
243  
244                  // load the memory stream into a bitmap image
245                  var bitmap = new BitmapImage();
246                  var rnd = stream.AsRandomAccessStream();
247                  bitmap.DecodePixelWidth = previewImage.Width;
248                  bitmap.DecodePixelHeight = previewImage.Height;
249                  bitmap.SetSource(rnd);
250                  return bitmap;
251              }
252          }
253  
254          public string MouseJumpPreviewType
255          {
256              get
257              {
258                  return MouseJumpSettingsConfig.Properties.PreviewType;
259              }
260  
261              set
262              {
263                  if (value != MouseJumpSettingsConfig.Properties.PreviewType)
264                  {
265                      MouseJumpSettingsConfig.Properties.PreviewType = value;
266                      NotifyMouseJumpPropertyChanged();
267                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
268                  }
269              }
270          }
271  
272          public string MouseJumpBackgroundColor1
273          {
274              get
275              {
276                  var value = MouseJumpSettingsConfig.Properties.BackgroundColor1;
277                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
278                  return value;
279              }
280  
281              set
282              {
283                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
284                  if (!value.Equals(MouseJumpSettingsConfig.Properties.BackgroundColor1, StringComparison.OrdinalIgnoreCase))
285                  {
286                      MouseJumpSettingsConfig.Properties.BackgroundColor1 = value;
287                      NotifyMouseJumpPropertyChanged();
288                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
289                  }
290              }
291          }
292  
293          public string MouseJumpBackgroundColor2
294          {
295              get
296              {
297                  var value = MouseJumpSettingsConfig.Properties.BackgroundColor2;
298                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
299                  return value;
300              }
301  
302              set
303              {
304                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
305                  if (!value.Equals(MouseJumpSettingsConfig.Properties.BackgroundColor2, StringComparison.OrdinalIgnoreCase))
306                  {
307                      MouseJumpSettingsConfig.Properties.BackgroundColor2 = value;
308                      NotifyMouseJumpPropertyChanged();
309                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
310                  }
311              }
312          }
313  
314          public int MouseJumpBorderThickness
315          {
316              get
317              {
318                  return MouseJumpSettingsConfig.Properties.BorderThickness;
319              }
320  
321              set
322              {
323                  if (value != MouseJumpSettingsConfig.Properties.BorderThickness)
324                  {
325                      MouseJumpSettingsConfig.Properties.BorderThickness = value;
326                      NotifyMouseJumpPropertyChanged();
327                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
328                  }
329              }
330          }
331  
332          public string MouseJumpBorderColor
333          {
334              get
335              {
336                  var value = MouseJumpSettingsConfig.Properties.BorderColor;
337                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
338                  return value;
339              }
340  
341              set
342              {
343                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
344                  if (!value.Equals(MouseJumpSettingsConfig.Properties.BorderColor, StringComparison.OrdinalIgnoreCase))
345                  {
346                      MouseJumpSettingsConfig.Properties.BorderColor = value;
347                      NotifyMouseJumpPropertyChanged();
348                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
349                  }
350              }
351          }
352  
353          public int MouseJumpBorder3dDepth
354          {
355              get
356              {
357                  return MouseJumpSettingsConfig.Properties.Border3dDepth;
358              }
359  
360              set
361              {
362                  if (value != MouseJumpSettingsConfig.Properties.Border3dDepth)
363                  {
364                      MouseJumpSettingsConfig.Properties.Border3dDepth = value;
365                      NotifyMouseJumpPropertyChanged();
366                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
367                  }
368              }
369          }
370  
371          public int MouseJumpBorderPadding
372          {
373              get
374              {
375                  return MouseJumpSettingsConfig.Properties.BorderPadding;
376              }
377  
378              set
379              {
380                  if (value != MouseJumpSettingsConfig.Properties.BorderPadding)
381                  {
382                      MouseJumpSettingsConfig.Properties.BorderPadding = value;
383                      NotifyMouseJumpPropertyChanged();
384                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
385                  }
386              }
387          }
388  
389          public int MouseJumpBezelThickness
390          {
391              get
392              {
393                  return MouseJumpSettingsConfig.Properties.BezelThickness;
394              }
395  
396              set
397              {
398                  if (value != MouseJumpSettingsConfig.Properties.BezelThickness)
399                  {
400                      MouseJumpSettingsConfig.Properties.BezelThickness = value;
401                      NotifyMouseJumpPropertyChanged();
402                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
403                  }
404              }
405          }
406  
407          public string MouseJumpBezelColor
408          {
409              get
410              {
411                  var value = MouseJumpSettingsConfig.Properties.BezelColor;
412                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
413                  return value;
414              }
415  
416              set
417              {
418                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
419                  if (!value.Equals(MouseJumpSettingsConfig.Properties.BezelColor, StringComparison.OrdinalIgnoreCase))
420                  {
421                      MouseJumpSettingsConfig.Properties.BezelColor = value;
422                      NotifyMouseJumpPropertyChanged();
423                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
424                  }
425              }
426          }
427  
428          public int MouseJumpBezel3dDepth
429          {
430              get
431              {
432                  return MouseJumpSettingsConfig.Properties.Bezel3dDepth;
433              }
434  
435              set
436              {
437                  if (value != MouseJumpSettingsConfig.Properties.Bezel3dDepth)
438                  {
439                      MouseJumpSettingsConfig.Properties.Bezel3dDepth = value;
440                      NotifyMouseJumpPropertyChanged();
441                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
442                  }
443              }
444          }
445  
446          public int MouseJumpScreenMargin
447          {
448              get
449              {
450                  return MouseJumpSettingsConfig.Properties.ScreenMargin;
451              }
452  
453              set
454              {
455                  if (value != MouseJumpSettingsConfig.Properties.ScreenMargin)
456                  {
457                      MouseJumpSettingsConfig.Properties.ScreenMargin = value;
458                      NotifyMouseJumpPropertyChanged();
459                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
460                  }
461              }
462          }
463  
464          public string MouseJumpScreenColor1
465          {
466              get
467              {
468                  var value = MouseJumpSettingsConfig.Properties.ScreenColor1;
469                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
470                  return value;
471              }
472  
473              set
474              {
475                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
476                  if (!value.Equals(MouseJumpSettingsConfig.Properties.ScreenColor1, StringComparison.OrdinalIgnoreCase))
477                  {
478                      MouseJumpSettingsConfig.Properties.ScreenColor1 = value;
479                      NotifyMouseJumpPropertyChanged();
480                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
481                  }
482              }
483          }
484  
485          public string MouseJumpScreenColor2
486          {
487              get
488              {
489                  var value = MouseJumpSettingsConfig.Properties.ScreenColor2;
490                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
491                  return value;
492              }
493  
494              set
495              {
496                  value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
497                  if (!value.Equals(MouseJumpSettingsConfig.Properties.ScreenColor2, StringComparison.OrdinalIgnoreCase))
498                  {
499                      MouseJumpSettingsConfig.Properties.ScreenColor2 = value;
500                      NotifyMouseJumpPropertyChanged();
501                      NotifyMouseJumpPropertyChanged(nameof(this.MouseJumpPreviewImage));
502                  }
503              }
504          }
505  
506          public void MouseJumpThumbnailSizePropertyChanged(object sender, PropertyChangedEventArgs e)
507          {
508              NotifyMouseJumpPropertyChanged(nameof(MouseJumpThumbnailSize));
509          }
510  
511          public void NotifyMouseJumpPropertyChanged([CallerMemberName] string propertyName = null)
512          {
513              OnPropertyChanged(propertyName);
514  
515              SndMouseJumpSettings outsettings = new SndMouseJumpSettings(MouseJumpSettingsConfig);
516              SndModuleSettings<SndMouseJumpSettings> ipcMessage = new SndModuleSettings<SndMouseJumpSettings>(outsettings);
517              SendConfigMSG(ipcMessage.ToJsonString());
518              SettingsUtils.SaveSettings(MouseJumpSettingsConfig.ToJsonString(), MouseJumpSettings.ModuleName);
519          }
520      }
521  }