PowerOcrViewModel.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.Globalization; 9 using System.Linq; 10 using System.Text.Json; 11 using System.Timers; 12 using global::PowerToys.GPOWrapper; 13 using ManagedCommon; 14 using Microsoft.PowerToys.Settings.UI.Helpers; 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.PowerToys.Settings.UI.SerializationContext; 19 using Windows.Globalization; 20 using Windows.Media.Ocr; 21 22 namespace Microsoft.PowerToys.Settings.UI.ViewModels 23 { 24 public partial class PowerOcrViewModel : PageViewModelBase 25 { 26 protected override string ModuleName => PowerOcrSettings.ModuleName; 27 28 private bool _disposed; 29 30 // Delay saving of settings in order to avoid calling save multiple times and hitting file in use exception. If there is no other request to save settings in given interval, we proceed to save it; otherwise, we schedule saving it after this interval 31 private const int SaveSettingsDelayInMs = 500; 32 33 private GeneralSettings GeneralSettingsConfig { get; set; } 34 35 private readonly SettingsUtils _settingsUtils; 36 private readonly System.Threading.Lock _delayedActionLock = new System.Threading.Lock(); 37 38 private readonly PowerOcrSettings _powerOcrSettings; 39 private Timer _delayedTimer; 40 41 private GpoRuleConfigured _enabledGpoRuleConfiguration; 42 private bool _enabledStateIsGPOConfigured; 43 private bool _isEnabled; 44 private int _languageIndex; 45 private List<Language> possibleOcrLanguages; 46 47 public ObservableCollection<string> AvailableLanguages { get; } = new ObservableCollection<string>(); 48 49 public int LanguageIndex 50 { 51 get 52 { 53 return _languageIndex; 54 } 55 56 set 57 { 58 if (value != _languageIndex) 59 { 60 _languageIndex = value; 61 if (_powerOcrSettings != null && _languageIndex < possibleOcrLanguages.Count && _languageIndex >= 0) 62 { 63 _powerOcrSettings.Properties.PreferredLanguage = possibleOcrLanguages[_languageIndex].NativeName; 64 NotifySettingsChanged(); 65 } 66 67 OnPropertyChanged(nameof(LanguageIndex)); 68 } 69 } 70 } 71 72 private Func<string, int> SendConfigMSG { get; } 73 74 public PowerOcrViewModel( 75 SettingsUtils settingsUtils, 76 ISettingsRepository<GeneralSettings> settingsRepository, 77 ISettingsRepository<PowerOcrSettings> powerOcrsettingsRepository, 78 Func<string, int> ipcMSGCallBackFunc) 79 { 80 // To obtain the general settings configurations of PowerToys Settings. 81 ArgumentNullException.ThrowIfNull(settingsRepository); 82 83 GeneralSettingsConfig = settingsRepository.SettingsConfig; 84 85 // To obtain the settings configurations of Fancy zones. 86 ArgumentNullException.ThrowIfNull(settingsRepository); 87 88 _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils)); 89 90 ArgumentNullException.ThrowIfNull(powerOcrsettingsRepository); 91 92 _powerOcrSettings = powerOcrsettingsRepository.SettingsConfig; 93 94 InitializeEnabledValue(); 95 96 // set the callback functions value to handle outgoing IPC message. 97 SendConfigMSG = ipcMSGCallBackFunc; 98 99 _delayedTimer = new Timer(); 100 _delayedTimer.Interval = SaveSettingsDelayInMs; 101 _delayedTimer.Elapsed += DelayedTimer_Tick; 102 _delayedTimer.AutoReset = false; 103 } 104 105 private void InitializeEnabledValue() 106 { 107 _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredTextExtractorEnabledValue(); 108 if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) 109 { 110 // Get the enabled state from GPO. 111 _enabledStateIsGPOConfigured = true; 112 _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; 113 } 114 else 115 { 116 _isEnabled = GeneralSettingsConfig.Enabled.PowerOcr; 117 } 118 } 119 120 public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings() 121 { 122 var hotkeysDict = new Dictionary<string, HotkeySettings[]> 123 { 124 [ModuleName] = [ActivationShortcut], 125 }; 126 127 return hotkeysDict; 128 } 129 130 public bool IsEnabled 131 { 132 get => _isEnabled; 133 set 134 { 135 if (_enabledStateIsGPOConfigured) 136 { 137 // If it's GPO configured, shouldn't be able to change this state. 138 return; 139 } 140 141 if (_isEnabled != value) 142 { 143 _isEnabled = value; 144 OnPropertyChanged(nameof(IsEnabled)); 145 146 // Set the status of PowerOcr in the general settings 147 GeneralSettingsConfig.Enabled.PowerOcr = value; 148 var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); 149 150 SendConfigMSG(outgoing.ToString()); 151 } 152 } 153 } 154 155 public bool IsWin11OrGreater 156 { 157 get => OSVersionHelper.IsWindows11(); 158 } 159 160 public bool IsEnabledGpoConfigured 161 { 162 get => _enabledStateIsGPOConfigured; 163 } 164 165 public HotkeySettings ActivationShortcut 166 { 167 get => _powerOcrSettings.Properties.ActivationShortcut; 168 set 169 { 170 if (_powerOcrSettings.Properties.ActivationShortcut != value) 171 { 172 _powerOcrSettings.Properties.ActivationShortcut = value ?? _powerOcrSettings.Properties.DefaultActivationShortcut; 173 OnPropertyChanged(nameof(ActivationShortcut)); 174 175 _settingsUtils.SaveSettings(_powerOcrSettings.ToJsonString(), PowerOcrSettings.ModuleName); 176 NotifySettingsChanged(); 177 } 178 } 179 } 180 181 internal void UpdateLanguages() 182 { 183 int preferredLanguageIndex = -1; 184 int systemLanguageIndex = -1; 185 CultureInfo systemCulture = CultureInfo.CurrentUICulture; 186 187 // get the list of all installed OCR languages. While processing them, search for the previously preferred language and also for the current ui language 188 possibleOcrLanguages = OcrEngine.AvailableRecognizerLanguages.OrderBy(x => x.NativeName).ToList(); 189 AvailableLanguages.Clear(); 190 foreach (Language language in possibleOcrLanguages) 191 { 192 if (_powerOcrSettings.Properties.PreferredLanguage?.Equals(language.DisplayName, StringComparison.Ordinal) == true) 193 { 194 preferredLanguageIndex = AvailableLanguages.Count; 195 } 196 197 if (systemCulture.DisplayName.Equals(language.DisplayName, StringComparison.Ordinal) || systemCulture.Parent.DisplayName.Equals(language.DisplayName, StringComparison.Ordinal)) 198 { 199 systemLanguageIndex = AvailableLanguages.Count; 200 } 201 202 AvailableLanguages.Add(EnsureStartUpper(language.NativeName)); 203 } 204 205 // if the previously stored preferred language is not available (has been deleted or this is the first run with language preference) 206 if (preferredLanguageIndex == -1) 207 { 208 // try to use the current ui language. If it is also not available, set the first language as preferred (to have any selected language) 209 if (systemLanguageIndex >= 0) 210 { 211 preferredLanguageIndex = systemLanguageIndex; 212 } 213 else 214 { 215 preferredLanguageIndex = 0; 216 } 217 } 218 219 // set the language index -> the preferred language gets selected in the combo box 220 LanguageIndex = preferredLanguageIndex; 221 } 222 223 private void ScheduleSavingOfSettings() 224 { 225 lock (_delayedActionLock) 226 { 227 if (_delayedTimer.Enabled) 228 { 229 _delayedTimer.Stop(); 230 } 231 232 _delayedTimer.Start(); 233 } 234 } 235 236 private void DelayedTimer_Tick(object sender, EventArgs e) 237 { 238 lock (_delayedActionLock) 239 { 240 _delayedTimer.Stop(); 241 NotifySettingsChanged(); 242 } 243 } 244 245 private void NotifySettingsChanged() 246 { 247 // Using InvariantCulture as this is an IPC message 248 SendConfigMSG( 249 string.Format( 250 CultureInfo.InvariantCulture, 251 "{{ \"powertoys\": {{ \"{0}\": {1} }} }}", 252 PowerOcrSettings.ModuleName, 253 JsonSerializer.Serialize(_powerOcrSettings, SourceGenerationContextContext.Default.PowerOcrSettings))); 254 } 255 256 public void RefreshEnabledState() 257 { 258 InitializeEnabledValue(); 259 OnPropertyChanged(nameof(IsEnabled)); 260 } 261 262 protected override void Dispose(bool disposing) 263 { 264 if (!_disposed) 265 { 266 if (disposing) 267 { 268 _delayedTimer?.Dispose(); 269 _delayedTimer = null; 270 } 271 272 _disposed = true; 273 } 274 275 base.Dispose(disposing); 276 } 277 278 public string SnippingToolInfoBarMargin 279 { 280 // Workaround for wrong StackPanel behavior: On hidden controls the margin is still reserved. 281 get => IsWin11OrGreater ? "0,0,0,25" : "0,0,0,0"; 282 } 283 284 private string EnsureStartUpper(string input) 285 { 286 if (string.IsNullOrEmpty(input)) 287 { 288 return input; 289 } 290 291 var inputArray = input.ToCharArray(); 292 inputArray[0] = char.ToUpper(inputArray[0], CultureInfo.CurrentCulture); 293 return new string(inputArray); 294 } 295 } 296 }