PowerLauncherViewModel.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.Collections.ObjectModel; 8 using System.ComponentModel; 9 using System.Globalization; 10 using System.Linq; 11 using System.Runtime.CompilerServices; 12 using System.Text.Json; 13 using System.Windows.Input; 14 using global::PowerToys.GPOWrapper; 15 using ManagedCommon; 16 using Microsoft.PowerToys.Settings.UI.Helpers; 17 using Microsoft.PowerToys.Settings.UI.Library; 18 using Microsoft.PowerToys.Settings.UI.Library.Helpers; 19 using Microsoft.PowerToys.Settings.UI.Library.Interfaces; 20 using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands; 21 using Microsoft.PowerToys.Settings.UI.SerializationContext; 22 23 namespace Microsoft.PowerToys.Settings.UI.ViewModels 24 { 25 public partial class PowerLauncherViewModel : PageViewModelBase, IDisposable 26 { 27 private int _themeIndex; 28 private int _monitorPositionIndex; 29 30 private GpoRuleConfigured _enabledGpoRuleConfiguration; 31 private bool _enabledStateIsGPOConfigured; 32 private bool _isEnabled; 33 private string _searchText; 34 private bool _hotkeyChanged; 35 36 private GeneralSettings GeneralSettingsConfig { get; set; } 37 38 private PowerLauncherSettings settings; 39 40 public delegate void SendCallback(PowerLauncherSettings settings); 41 42 protected override string ModuleName => PowerLauncherSettings.ModuleName; 43 44 private readonly SendCallback callback; 45 46 private readonly Func<bool> isDark; 47 48 private Func<string, int> SendConfigMSG { get; } 49 50 public PowerLauncherViewModel( 51 PowerLauncherSettings settings, 52 ISettingsRepository<GeneralSettings> settingsRepository, 53 Func<string, int> ipcMSGCallBackFunc, 54 Func<bool> isDark) 55 { 56 if (settings == null) 57 { 58 throw new ArgumentException("settings argument cannot be null"); 59 } 60 61 this.settings = settings; 62 this.isDark = isDark; 63 64 // To obtain the general Settings configurations of PowerToys 65 ArgumentNullException.ThrowIfNull(settingsRepository); 66 67 GeneralSettingsConfig = settingsRepository.SettingsConfig; 68 69 InitializeEnabledValue(); 70 71 // set the callback functions value to handle outgoing IPC message. 72 SendConfigMSG = ipcMSGCallBackFunc; 73 callback = (PowerLauncherSettings s) => 74 { 75 // Propagate changes to Power Launcher through IPC 76 // Using InvariantCulture as this is an IPC message 77 SendConfigMSG( 78 string.Format( 79 CultureInfo.InvariantCulture, 80 "{{ \"powertoys\": {{ \"{0}\": {1} }} }}", 81 PowerLauncherSettings.ModuleName, 82 JsonSerializer.Serialize(s, SourceGenerationContextContext.Default.PowerLauncherSettings))); 83 }; 84 85 switch (settings.Properties.Theme) 86 { 87 case Theme.Dark: 88 _themeIndex = 0; 89 break; 90 case Theme.Light: 91 _themeIndex = 1; 92 break; 93 case Theme.System: 94 _themeIndex = 2; 95 break; 96 } 97 98 switch (settings.Properties.Position) 99 { 100 case StartupPosition.Cursor: 101 _monitorPositionIndex = 0; 102 break; 103 case StartupPosition.PrimaryMonitor: 104 _monitorPositionIndex = 1; 105 break; 106 case StartupPosition.Focus: 107 _monitorPositionIndex = 2; 108 break; 109 } 110 111 SearchPluginsCommand = new Library.ViewModels.Commands.RelayCommand(SearchPlugins); 112 } 113 114 private void InitializeEnabledValue() 115 { 116 _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPowerLauncherEnabledValue(); 117 if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) 118 { 119 // Get the enabled state from GPO. 120 _enabledStateIsGPOConfigured = true; 121 _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; 122 } 123 else 124 { 125 _isEnabled = GeneralSettingsConfig.Enabled.PowerLauncher; 126 } 127 } 128 129 public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings() 130 { 131 var hotkeysDict = new Dictionary<string, HotkeySettings[]> 132 { 133 [ModuleName] = [OpenPowerLauncher], 134 }; 135 136 return hotkeysDict; 137 } 138 139 private void OnPluginInfoChange(object sender, PropertyChangedEventArgs e) 140 { 141 if ( 142 e.PropertyName == nameof(PowerLauncherPluginViewModel.ShowNotAccessibleWarning) 143 || e.PropertyName == nameof(PowerLauncherPluginViewModel.ShowBadgeOnPluginSettingError) 144 ) 145 { 146 // Don't trigger a settings update if the changed property is for visual notification. 147 return; 148 } 149 150 OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); 151 OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); 152 UpdateSettings(); 153 } 154 155 public PowerLauncherViewModel(PowerLauncherSettings settings, SendCallback callback) 156 { 157 this.settings = settings; 158 this.callback = callback; 159 } 160 161 private void UpdateSettings([CallerMemberName] string propertyName = null) 162 { 163 // Notify UI of property change 164 OnPropertyChanged(propertyName); 165 166 // Since PowerLauncher registers its hotkeys independently within the module process, 167 // the runner is notified to update PowerLauncher�s hotkeys only when changes occur. 168 // This prevents incorrect conflict detection results. 169 settings.Properties.HotkeyChanged = _hotkeyChanged; 170 _hotkeyChanged = false; 171 172 callback(settings); 173 } 174 175 public bool EnablePowerLauncher 176 { 177 get 178 { 179 return _isEnabled; 180 } 181 182 set 183 { 184 if (_enabledStateIsGPOConfigured) 185 { 186 // If it's GPO configured, shouldn't be able to change this state. 187 return; 188 } 189 190 if (_isEnabled != value) 191 { 192 _isEnabled = value; 193 GeneralSettingsConfig.Enabled.PowerLauncher = value; 194 OnPropertyChanged(nameof(EnablePowerLauncher)); 195 OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); 196 OnPropertyChanged(nameof(ShowPluginsLoadingMessage)); 197 OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); 198 OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); 199 SendConfigMSG(outgoing.ToString()); 200 } 201 } 202 } 203 204 public void RefreshEnabledState() 205 { 206 InitializeEnabledValue(); 207 OnPropertyChanged(nameof(EnablePowerLauncher)); 208 OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); 209 OnPropertyChanged(nameof(ShowPluginsLoadingMessage)); 210 OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); 211 } 212 213 public bool IsEnabledGpoConfigured 214 { 215 get => _enabledStateIsGPOConfigured; 216 } 217 218 public string SearchResultPreference 219 { 220 get 221 { 222 return settings.Properties.SearchResultPreference; 223 } 224 225 set 226 { 227 if (settings.Properties.SearchResultPreference != value) 228 { 229 settings.Properties.SearchResultPreference = value; 230 UpdateSettings(); 231 } 232 } 233 } 234 235 public string SearchTypePreference 236 { 237 get 238 { 239 return settings.Properties.SearchTypePreference; 240 } 241 242 set 243 { 244 if (settings.Properties.SearchTypePreference != value) 245 { 246 settings.Properties.SearchTypePreference = value; 247 UpdateSettings(); 248 } 249 } 250 } 251 252 public int MaximumNumberOfResults 253 { 254 get 255 { 256 return settings.Properties.MaximumNumberOfResults; 257 } 258 259 set 260 { 261 if (settings.Properties.MaximumNumberOfResults != value) 262 { 263 settings.Properties.MaximumNumberOfResults = value; 264 UpdateSettings(); 265 } 266 } 267 } 268 269 public int ThemeIndex 270 { 271 get 272 { 273 return _themeIndex; 274 } 275 276 set 277 { 278 switch (value) 279 { 280 case 0: settings.Properties.Theme = Theme.Dark; break; 281 case 1: settings.Properties.Theme = Theme.Light; break; 282 case 2: settings.Properties.Theme = Theme.System; break; 283 } 284 285 _themeIndex = value; 286 UpdateSettings(); 287 } 288 } 289 290 public int MonitorPositionIndex 291 { 292 get 293 { 294 return _monitorPositionIndex; 295 } 296 297 set 298 { 299 if (_monitorPositionIndex != value) 300 { 301 switch (value) 302 { 303 case 0: settings.Properties.Position = StartupPosition.Cursor; break; 304 case 1: settings.Properties.Position = StartupPosition.PrimaryMonitor; break; 305 case 2: settings.Properties.Position = StartupPosition.Focus; break; 306 } 307 308 _monitorPositionIndex = value; 309 UpdateSettings(); 310 } 311 } 312 } 313 314 public string SearchText 315 { 316 get 317 { 318 return _searchText; 319 } 320 321 set 322 { 323 if (_searchText != value) 324 { 325 _searchText = value; 326 OnPropertyChanged(nameof(SearchText)); 327 } 328 } 329 } 330 331 public ICommand SearchPluginsCommand { get; } 332 333 public HotkeySettings OpenPowerLauncher 334 { 335 get 336 { 337 return settings.Properties.OpenPowerLauncher; 338 } 339 340 set 341 { 342 if (settings.Properties.OpenPowerLauncher != value) 343 { 344 settings.Properties.OpenPowerLauncher = value ?? settings.Properties.DefaultOpenPowerLauncher; 345 _hotkeyChanged = true; 346 UpdateSettings(); 347 } 348 } 349 } 350 351 public bool UseCentralizedKeyboardHook 352 { 353 get 354 { 355 return settings.Properties.UseCentralizedKeyboardHook; 356 } 357 358 set 359 { 360 if (settings.Properties.UseCentralizedKeyboardHook != value) 361 { 362 settings.Properties.UseCentralizedKeyboardHook = value; 363 UpdateSettings(); 364 } 365 } 366 } 367 368 public bool SearchQueryResultsWithDelay 369 { 370 get 371 { 372 return settings.Properties.SearchQueryResultsWithDelay; 373 } 374 375 set 376 { 377 if (settings.Properties.SearchQueryResultsWithDelay != value) 378 { 379 settings.Properties.SearchQueryResultsWithDelay = value; 380 UpdateSettings(); 381 } 382 } 383 } 384 385 public int SearchInputDelayFast 386 { 387 get 388 { 389 return settings.Properties.SearchInputDelayFast; 390 } 391 392 set 393 { 394 if (settings.Properties.SearchInputDelayFast != value) 395 { 396 settings.Properties.SearchInputDelayFast = value; 397 UpdateSettings(); 398 } 399 } 400 } 401 402 public int SearchInputDelay 403 { 404 get 405 { 406 return settings.Properties.SearchInputDelay; 407 } 408 409 set 410 { 411 if (settings.Properties.SearchInputDelay != value) 412 { 413 settings.Properties.SearchInputDelay = value; 414 UpdateSettings(); 415 } 416 } 417 } 418 419 public bool SearchQueryTuningEnabled 420 { 421 get 422 { 423 return settings.Properties.SearchQueryTuningEnabled; 424 } 425 426 set 427 { 428 if (settings.Properties.SearchQueryTuningEnabled != value) 429 { 430 settings.Properties.SearchQueryTuningEnabled = value; 431 UpdateSettings(); 432 } 433 } 434 } 435 436 public bool SearchWaitForSlowResults 437 { 438 get 439 { 440 return settings.Properties.SearchWaitForSlowResults; 441 } 442 443 set 444 { 445 if (settings.Properties.SearchWaitForSlowResults != value) 446 { 447 settings.Properties.SearchWaitForSlowResults = value; 448 UpdateSettings(); 449 } 450 } 451 } 452 453 public int SearchClickedItemWeight 454 { 455 get 456 { 457 return settings.Properties.SearchClickedItemWeight; 458 } 459 460 set 461 { 462 if (settings.Properties.SearchClickedItemWeight != value) 463 { 464 settings.Properties.SearchClickedItemWeight = value; 465 UpdateSettings(); 466 } 467 } 468 } 469 470 public HotkeySettings OpenFileLocation 471 { 472 get 473 { 474 return settings.Properties.OpenFileLocation; 475 } 476 477 set 478 { 479 if (settings.Properties.OpenFileLocation != value) 480 { 481 settings.Properties.OpenFileLocation = value ?? settings.Properties.DefaultOpenFileLocation; 482 UpdateSettings(); 483 } 484 } 485 } 486 487 public HotkeySettings CopyPathLocation 488 { 489 get 490 { 491 return settings.Properties.CopyPathLocation; 492 } 493 494 set 495 { 496 if (settings.Properties.CopyPathLocation != value) 497 { 498 settings.Properties.CopyPathLocation = value ?? settings.Properties.DefaultCopyPathLocation; 499 UpdateSettings(); 500 } 501 } 502 } 503 504 public bool OverrideWinRKey 505 { 506 get 507 { 508 return settings.Properties.OverrideWinkeyR; 509 } 510 511 set 512 { 513 if (settings.Properties.OverrideWinkeyR != value) 514 { 515 settings.Properties.OverrideWinkeyR = value; 516 UpdateSettings(); 517 } 518 } 519 } 520 521 public bool OverrideWinSKey 522 { 523 get 524 { 525 return settings.Properties.OverrideWinkeyS; 526 } 527 528 set 529 { 530 if (settings.Properties.OverrideWinkeyS != value) 531 { 532 settings.Properties.OverrideWinkeyS = value; 533 UpdateSettings(); 534 } 535 } 536 } 537 538 public bool IgnoreHotkeysInFullScreen 539 { 540 get 541 { 542 return settings.Properties.IgnoreHotkeysInFullscreen; 543 } 544 545 set 546 { 547 if (settings.Properties.IgnoreHotkeysInFullscreen != value) 548 { 549 settings.Properties.IgnoreHotkeysInFullscreen = value; 550 UpdateSettings(); 551 } 552 } 553 } 554 555 public bool ClearInputOnLaunch 556 { 557 get 558 { 559 return settings.Properties.ClearInputOnLaunch; 560 } 561 562 set 563 { 564 if (settings.Properties.ClearInputOnLaunch != value) 565 { 566 settings.Properties.ClearInputOnLaunch = value; 567 UpdateSettings(); 568 } 569 } 570 } 571 572 public bool TabSelectsContextButtons 573 { 574 get 575 { 576 return settings.Properties.TabSelectsContextButtons; 577 } 578 579 set 580 { 581 if (settings.Properties.TabSelectsContextButtons != value) 582 { 583 settings.Properties.TabSelectsContextButtons = value; 584 UpdateSettings(); 585 } 586 } 587 } 588 589 public bool GenerateThumbnailsFromFiles 590 { 591 get 592 { 593 return settings.Properties.GenerateThumbnailsFromFiles; 594 } 595 596 set 597 { 598 if (settings.Properties.GenerateThumbnailsFromFiles != value) 599 { 600 settings.Properties.GenerateThumbnailsFromFiles = value; 601 UpdateSettings(); 602 } 603 } 604 } 605 606 public bool UsePinyin 607 { 608 get 609 { 610 return settings.Properties.UsePinyin; 611 } 612 613 set 614 { 615 if (settings.Properties.UsePinyin != value) 616 { 617 settings.Properties.UsePinyin = value; 618 UpdateSettings(); 619 } 620 } 621 } 622 623 public int ShowPluginsOverviewIndex 624 { 625 get 626 { 627 return settings.Properties.ShowPluginsOverview; 628 } 629 630 set 631 { 632 if (settings.Properties.ShowPluginsOverview != value) 633 { 634 settings.Properties.ShowPluginsOverview = value; 635 UpdateSettings(); 636 } 637 } 638 } 639 640 public int TitleFontSize 641 { 642 get 643 { 644 return settings.Properties.TitleFontSize; 645 } 646 647 set 648 { 649 if (settings.Properties.TitleFontSize != value) 650 { 651 settings.Properties.TitleFontSize = value; 652 UpdateSettings(); 653 } 654 } 655 } 656 657 private ObservableCollection<PowerLauncherPluginViewModel> _plugins; 658 659 public ObservableCollection<PowerLauncherPluginViewModel> Plugins 660 { 661 get 662 { 663 if (_plugins == null) 664 { 665 _plugins = new ObservableCollection<PowerLauncherPluginViewModel>(settings.Plugins.Select(x => new PowerLauncherPluginViewModel(x, isDark))); 666 foreach (var plugin in Plugins) 667 { 668 plugin.PropertyChanged += OnPluginInfoChange; 669 } 670 } 671 672 return _plugins; 673 } 674 } 675 676 public bool ShowPluginsAreGpoManagedInfo 677 { 678 get => EnablePowerLauncher && settings.Plugins.Any() && Plugins.Any(x => x.EnabledGpoRuleIsConfigured); 679 } 680 681 public bool ShowAllPluginsDisabledWarning 682 { 683 get => EnablePowerLauncher && settings.Plugins.Any() && Plugins.All(x => x.Disabled); 684 } 685 686 public bool ShowPluginsLoadingMessage 687 { 688 get => EnablePowerLauncher && !Plugins.Any(); 689 } 690 691 public bool IsUpToDate(PowerLauncherSettings settings) 692 { 693 return this.settings.Equals(settings); 694 } 695 696 public void SearchPlugins() 697 { 698 if (!string.IsNullOrWhiteSpace(SearchText)) 699 { 700 var plugins = settings.Plugins.Where(p => p.Name.StartsWith(SearchText, StringComparison.OrdinalIgnoreCase) || p.Name.IndexOf($" {SearchText}", StringComparison.OrdinalIgnoreCase) > 0); 701 _plugins = new ObservableCollection<PowerLauncherPluginViewModel>(plugins.Select(x => new PowerLauncherPluginViewModel(x, isDark))); 702 foreach (var plugin in _plugins) 703 { 704 plugin.PropertyChanged += OnPluginInfoChange; 705 } 706 } 707 else 708 { 709 _plugins = null; 710 } 711 712 OnPropertyChanged(nameof(Plugins)); 713 } 714 } 715 }