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 }