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