/ src / settings-ui / Settings.UI / ViewModels / MouseWithoutBordersViewModel.cs
MouseWithoutBordersViewModel.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.Globalization;
   8  using System.IO;
   9  using System.IO.Pipes;
  10  using System.Linq;
  11  using System.Net;
  12  using System.Runtime.CompilerServices;
  13  using System.Text.Json;
  14  using System.Threading;
  15  using System.Threading.Tasks;
  16  using global::PowerToys.GPOWrapper;
  17  using ManagedCommon;
  18  using Microsoft.PowerToys.Settings.UI.Helpers;
  19  using Microsoft.PowerToys.Settings.UI.Library;
  20  using Microsoft.PowerToys.Settings.UI.Library.Helpers;
  21  using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
  22  using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
  23  using Microsoft.PowerToys.Settings.UI.SerializationContext;
  24  using Microsoft.UI;
  25  using Microsoft.UI.Dispatching;
  26  using Microsoft.UI.Xaml.Media;
  27  using StreamJsonRpc;
  28  using Windows.ApplicationModel.DataTransfer;
  29  
  30  namespace Microsoft.PowerToys.Settings.UI.ViewModels
  31  {
  32      public partial class MouseWithoutBordersViewModel : PageViewModelBase
  33      {
  34          protected override string ModuleName => MouseWithoutBordersSettings.ModuleName;
  35  
  36          // These should be in the same order as the ComboBoxItems in MouseWithoutBordersPage.xaml switch machine shortcut options
  37          private readonly int[] _switchBetweenMachineShortcutOptions =
  38          {
  39              112,
  40              49,
  41              0,
  42          };
  43  
  44          private readonly Lock _machineMatrixStringLock = new();
  45  
  46          private bool _disposed;
  47  
  48          private static readonly Dictionary<SocketStatus, Brush> StatusColors = new Dictionary<SocketStatus, Brush>()
  49          {
  50              { SocketStatus.NA, new SolidColorBrush(ColorHelper.FromArgb(0, 0x71, 0x71, 0x71)) },
  51              { SocketStatus.Resolving, new SolidColorBrush(Colors.Yellow) },
  52              { SocketStatus.Connecting, new SolidColorBrush(Colors.Orange) },
  53              { SocketStatus.Handshaking, new SolidColorBrush(Colors.Blue) },
  54              { SocketStatus.Error, new SolidColorBrush(Colors.Red) },
  55              { SocketStatus.ForceClosed, new SolidColorBrush(Colors.Purple) },
  56              { SocketStatus.InvalidKey, new SolidColorBrush(Colors.Brown) },
  57              { SocketStatus.Timeout, new SolidColorBrush(Colors.Pink) },
  58              { SocketStatus.SendError, new SolidColorBrush(Colors.Maroon) },
  59              { SocketStatus.Connected, new SolidColorBrush(Colors.Green) },
  60          };
  61  
  62          private bool _connectFieldsVisible;
  63  
  64          public bool IsElevated { get => GeneralSettingsConfig.IsElevated; }
  65  
  66          public bool CanUninstallService { get => GeneralSettingsConfig.IsElevated && !UseService; }
  67  
  68          public ButtonClickCommand AddFirewallRuleEventHandler => new ButtonClickCommand(AddFirewallRule);
  69  
  70          public ButtonClickCommand UninstallServiceEventHandler => new ButtonClickCommand(UninstallService);
  71  
  72          public bool ShowOriginalUI
  73          {
  74              get
  75              {
  76                  if (_useOriginalUserInterfaceGpoConfiguration == GpoRuleConfigured.Disabled)
  77                  {
  78                      return false;
  79                  }
  80  
  81                  return Settings.Properties.ShowOriginalUI;
  82              }
  83  
  84              set
  85              {
  86                  if (!_useOriginalUserInterfaceIsGPOConfigured && (Settings.Properties.ShowOriginalUI != value))
  87                  {
  88                      Settings.Properties.ShowOriginalUI = value;
  89                      NotifyPropertyChanged(nameof(ShowOriginalUI));
  90                  }
  91              }
  92          }
  93  
  94          public bool CardForOriginalUiSettingIsEnabled => _useOriginalUserInterfaceIsGPOConfigured == false;
  95  
  96          public bool ShowPolicyConfiguredInfoForOriginalUiSetting => IsEnabled && _useOriginalUserInterfaceIsGPOConfigured;
  97  
  98          public bool UseService
  99          {
 100              get
 101              {
 102                  if (_allowServiceModeGpoConfiguration == GpoRuleConfigured.Disabled)
 103                  {
 104                      return false;
 105                  }
 106  
 107                  return Settings.Properties.UseService;
 108              }
 109  
 110              set
 111              {
 112                  if (_allowServiceModeIsGPOConfigured)
 113                  {
 114                      return;
 115                  }
 116  
 117                  var valueChanged = Settings.Properties.UseService != value;
 118  
 119                  // Set the UI property itself instantly
 120                  if (valueChanged)
 121                  {
 122                      Settings.Properties.UseService = value;
 123                      OnPropertyChanged(nameof(UseService));
 124                      OnPropertyChanged(nameof(CanUninstallService));
 125                      OnPropertyChanged(nameof(ShowInfobarRunAsAdminText));
 126  
 127                      // Must block here until the process exits
 128                      Task.Run(async () =>
 129                      {
 130                          await SubmitShutdownRequestAsync();
 131  
 132                          _uiDispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
 133                          {
 134                              Settings.Properties.UseService = value;
 135                              NotifyPropertyChanged(nameof(UseService));
 136                          });
 137                      });
 138                  }
 139              }
 140          }
 141  
 142          public bool UseServiceSettingIsEnabled => _allowServiceModeIsGPOConfigured == false;
 143  
 144          public bool ConnectFieldsVisible
 145          {
 146              get => _connectFieldsVisible;
 147  
 148              set
 149              {
 150                  if (_connectFieldsVisible != value)
 151                  {
 152                      _connectFieldsVisible = value;
 153                      OnPropertyChanged(nameof(ConnectFieldsVisible));
 154                  }
 155              }
 156          }
 157  
 158          private string _connectSecurityKey;
 159  
 160          public string ConnectSecurityKey
 161          {
 162              get => _connectSecurityKey;
 163  
 164              set
 165              {
 166                  if (_connectSecurityKey != value)
 167                  {
 168                      _connectSecurityKey = value;
 169                      OnPropertyChanged(nameof(ConnectSecurityKey));
 170                  }
 171              }
 172          }
 173  
 174          private string _connectPCName;
 175  
 176          public string ConnectPCName
 177          {
 178              get => _connectPCName;
 179  
 180              set
 181              {
 182                  if (_connectPCName != value)
 183                  {
 184                      _connectPCName = value;
 185                      OnPropertyChanged(nameof(ConnectPCName));
 186                  }
 187              }
 188          }
 189  
 190          private SettingsUtils SettingsUtils { get; set; }
 191  
 192          private GeneralSettings GeneralSettingsConfig { get; set; }
 193  
 194          private GpoRuleConfigured _enabledGpoRuleConfiguration;
 195          private bool _enabledStateIsGPOConfigured;
 196          private bool _isEnabled;
 197  
 198          // Configuration policy variables
 199          private GpoRuleConfigured _clipboardSharingEnabledGpoConfiguration;
 200          private bool _clipboardSharingEnabledIsGPOConfigured;
 201          private GpoRuleConfigured _fileTransferEnabledGpoConfiguration;
 202          private bool _fileTransferEnabledIsGPOConfigured;
 203          private GpoRuleConfigured _useOriginalUserInterfaceGpoConfiguration;
 204          private bool _useOriginalUserInterfaceIsGPOConfigured;
 205          private GpoRuleConfigured _disallowBlockingScreensaverGpoConfiguration;
 206          private bool _disallowBlockingScreensaverIsGPOConfigured;
 207          private GpoRuleConfigured _allowServiceModeGpoConfiguration;
 208          private bool _allowServiceModeIsGPOConfigured;
 209          private GpoRuleConfigured _sameSubnetOnlyGpoConfiguration;
 210          private bool _sameSubnetOnlyIsGPOConfigured;
 211          private GpoRuleConfigured _validateRemoteIpGpoConfiguration;
 212          private bool _validateRemoteIpIsGPOConfigured;
 213          private GpoRuleConfigured _disableUserDefinedIpMappingRulesGpoConfiguration;
 214          private bool _disableUserDefinedIpMappingRulesIsGPOConfigured;
 215          private string _policyDefinedIpMappingRulesGPOData;
 216          private bool _policyDefinedIpMappingRulesIsGPOConfigured;
 217  
 218          public string MachineHostName
 219          {
 220              get
 221              {
 222                  try
 223                  {
 224                      return Dns.GetHostName();
 225                  }
 226                  catch
 227                  {
 228                      return string.Empty;
 229                  }
 230              }
 231          }
 232  
 233          public bool IsEnabledGpoConfigured
 234          {
 235              get => _enabledStateIsGPOConfigured;
 236          }
 237  
 238          private enum SocketStatus : int
 239          {
 240              NA = 0,
 241              Resolving = 1,
 242              Connecting = 2,
 243              Handshaking = 3,
 244              Error = 4,
 245              ForceClosed = 5,
 246              InvalidKey = 6,
 247              Timeout = 7,
 248              SendError = 8,
 249              Connected = 9,
 250          }
 251  
 252          private interface ISettingsSyncHelper
 253          {
 254              [Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)]
 255              public struct MachineSocketState
 256              {
 257                  // Disable false-positive warning due to IPC
 258  #pragma warning disable CS0649
 259                  [Newtonsoft.Json.JsonProperty]
 260                  public string Name;
 261  
 262                  [Newtonsoft.Json.JsonProperty]
 263                  public SocketStatus Status;
 264  #pragma warning restore CS0649
 265              }
 266  
 267              void Shutdown();
 268  
 269              void Reconnect();
 270  
 271              void GenerateNewKey();
 272  
 273              void ConnectToMachine(string machineName, string securityKey);
 274  
 275              Task<MachineSocketState[]> RequestMachineSocketStateAsync();
 276          }
 277  
 278          private static CancellationTokenSource _cancellationTokenSource;
 279  
 280          private static Task _machinePollingThreadTask;
 281  
 282          private static VisualStudio.Threading.AsyncSemaphore _ipcSemaphore = new VisualStudio.Threading.AsyncSemaphore(1);
 283  
 284          private sealed partial class SyncHelper : IDisposable
 285          {
 286              public SyncHelper(NamedPipeClientStream stream)
 287              {
 288                  Stream = stream;
 289                  Endpoint = JsonRpc.Attach<ISettingsSyncHelper>(Stream);
 290              }
 291  
 292              public NamedPipeClientStream Stream { get; }
 293  
 294              public ISettingsSyncHelper Endpoint { get; private set; }
 295  
 296              public void Dispose()
 297              {
 298                  ((IDisposable)Endpoint).Dispose();
 299              }
 300          }
 301  
 302          private static NamedPipeClientStream syncHelperStream;
 303  
 304          private async Task<SyncHelper> GetSettingsSyncHelperAsync()
 305          {
 306              try
 307              {
 308                  var recreateStream = false;
 309                  if (syncHelperStream == null)
 310                  {
 311                      recreateStream = true;
 312                  }
 313                  else
 314                  {
 315                      if (!syncHelperStream.IsConnected || !syncHelperStream.CanWrite)
 316                      {
 317                          await syncHelperStream.DisposeAsync();
 318                          recreateStream = true;
 319                      }
 320                  }
 321  
 322                  if (recreateStream)
 323                  {
 324                      syncHelperStream = new NamedPipeClientStream(".", "MouseWithoutBorders/SettingsSync", PipeDirection.InOut, PipeOptions.Asynchronous);
 325                      await syncHelperStream.ConnectAsync(10000);
 326                  }
 327  
 328                  return new SyncHelper(syncHelperStream);
 329              }
 330              catch (Exception ex)
 331              {
 332                  if (IsEnabled)
 333                  {
 334                      Logger.LogError($"Couldn't create SettingsSync: {ex}");
 335                  }
 336  
 337                  return null;
 338              }
 339          }
 340  
 341          public async Task SubmitShutdownRequestAsync()
 342          {
 343              using (await _ipcSemaphore.EnterAsync())
 344              {
 345                  using (var syncHelper = await GetSettingsSyncHelperAsync())
 346                  {
 347                      syncHelper?.Endpoint?.Shutdown();
 348                      var task = syncHelper?.Stream.FlushAsync();
 349                      if (task != null)
 350                      {
 351                          await task;
 352                      }
 353                  }
 354              }
 355          }
 356  
 357          public async Task SubmitReconnectRequestAsync()
 358          {
 359              using (await _ipcSemaphore.EnterAsync())
 360              {
 361                  using (var syncHelper = await GetSettingsSyncHelperAsync())
 362                  {
 363                      syncHelper?.Endpoint?.Reconnect();
 364                      var task = syncHelper?.Stream.FlushAsync();
 365                      if (task != null)
 366                      {
 367                          await task;
 368                      }
 369                  }
 370              }
 371          }
 372  
 373          public async Task SubmitNewKeyRequestAsync()
 374          {
 375              using (await _ipcSemaphore.EnterAsync())
 376              {
 377                  using (var syncHelper = await GetSettingsSyncHelperAsync())
 378                  {
 379                      syncHelper?.Endpoint?.GenerateNewKey();
 380                      var task = syncHelper?.Stream.FlushAsync();
 381                      if (task != null)
 382                      {
 383                          await task;
 384                      }
 385                  }
 386              }
 387          }
 388  
 389          public async Task SubmitConnectionRequestAsync(string pcName, string securityKey)
 390          {
 391              using (await _ipcSemaphore.EnterAsync())
 392              {
 393                  using (var syncHelper = await GetSettingsSyncHelperAsync())
 394                  {
 395                      syncHelper?.Endpoint?.ConnectToMachine(pcName, securityKey);
 396                      var task = syncHelper?.Stream.FlushAsync();
 397                      if (task != null)
 398                      {
 399                          await task;
 400                      }
 401                  }
 402              }
 403          }
 404  
 405          private async Task<ISettingsSyncHelper.MachineSocketState[]> PollMachineSocketStateAsync()
 406          {
 407              using (await _ipcSemaphore.EnterAsync())
 408              {
 409                  using (var syncHelper = await GetSettingsSyncHelperAsync())
 410                  {
 411                      var task = syncHelper?.Endpoint?.RequestMachineSocketStateAsync();
 412                      if (task != null)
 413                      {
 414                          return await task;
 415                      }
 416                      else
 417                      {
 418                          return null;
 419                      }
 420                  }
 421              }
 422          }
 423  
 424          private MouseWithoutBordersSettings Settings { get; set; }
 425  
 426          private DispatcherQueue _uiDispatcherQueue;
 427  
 428          public MouseWithoutBordersViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, DispatcherQueue uiDispatcherQueue)
 429          {
 430              SettingsUtils = settingsUtils;
 431  
 432              _uiDispatcherQueue = uiDispatcherQueue;
 433  
 434              // To obtain the general settings configurations of PowerToys Settings.
 435              ArgumentNullException.ThrowIfNull(settingsRepository);
 436  
 437              GeneralSettingsConfig = settingsRepository.SettingsConfig;
 438  
 439              InitializeEnabledValue();
 440              InitializePolicyValues();
 441  
 442              // MouseWithoutBorders settings may be changed by the logic in the utility as machines connect. We need to get a fresh version every time instead of using a repository.
 443              MouseWithoutBordersSettings moduleSettings;
 444              moduleSettings = SettingsUtils.GetSettingsOrDefault<MouseWithoutBordersSettings>("MouseWithoutBorders");
 445  
 446              LoadViewModelFromSettings(moduleSettings);
 447  
 448              // set the callback functions value to handle outgoing IPC message.
 449              SendConfigMSG = ipcMSGCallBackFunc;
 450  
 451              _cancellationTokenSource?.Cancel();
 452  
 453              _cancellationTokenSource = new CancellationTokenSource();
 454  
 455              _machinePollingThreadTask = StartMachineStatusPollingThread(_machinePollingThreadTask, _cancellationTokenSource.Token);
 456          }
 457  
 458          private Task StartMachineStatusPollingThread(Task previousThreadTask, CancellationToken token)
 459          {
 460              return Task.Run(
 461                  async () =>
 462                  {
 463                      previousThreadTask?.Wait();
 464  
 465                      while (!token.IsCancellationRequested)
 466                      {
 467                          Dictionary<string, ISettingsSyncHelper.MachineSocketState> states = null;
 468                          try
 469                          {
 470                              states = (await PollMachineSocketStateAsync())?.ToDictionary(s => s.Name, StringComparer.OrdinalIgnoreCase);
 471                          }
 472                          catch (Exception ex)
 473                          {
 474                              Logger.LogInfo($"Poll ISettingsSyncHelper.MachineSocketState error: {ex}");
 475                              continue;
 476                          }
 477  
 478                          if (states != null)
 479                          {
 480                              lock (_machineMatrixStringLock)
 481                              {
 482                                  foreach (var machine in machineMatrixString)
 483                                  {
 484                                      if (states.TryGetValue(machine.Item.Name, out var state))
 485                                      {
 486                                          _uiDispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
 487                                          {
 488                                              try
 489                                              {
 490                                                  machine.Item.StatusBrush = StatusColors[state.Status];
 491                                              }
 492                                              catch (Exception)
 493                                              {
 494                                              }
 495                                          });
 496                                      }
 497                                  }
 498                              }
 499                          }
 500  
 501                          Thread.Sleep(500);
 502                      }
 503                  },
 504                  _cancellationTokenSource.Token);
 505          }
 506  
 507          private void InitializeEnabledValue()
 508          {
 509              _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue();
 510              if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
 511              {
 512                  // Get the enabled state from GPO.
 513                  _enabledStateIsGPOConfigured = true;
 514                  _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
 515              }
 516              else
 517              {
 518                  _isEnabled = GeneralSettingsConfig.Enabled.MouseWithoutBorders;
 519              }
 520          }
 521  
 522          private void InitializePolicyValues()
 523          {
 524              // Policies supporting only enabled state
 525              _disallowBlockingScreensaverGpoConfiguration = GPOWrapper.GetConfiguredMwbDisallowBlockingScreensaverValue();
 526              _disallowBlockingScreensaverIsGPOConfigured = _disallowBlockingScreensaverGpoConfiguration == GpoRuleConfigured.Enabled;
 527              _disableUserDefinedIpMappingRulesGpoConfiguration = GPOWrapper.GetConfiguredMwbDisableUserDefinedIpMappingRulesValue();
 528              _disableUserDefinedIpMappingRulesIsGPOConfigured = _disableUserDefinedIpMappingRulesGpoConfiguration == GpoRuleConfigured.Enabled;
 529  
 530              // Policies supporting only disabled state
 531              _allowServiceModeGpoConfiguration = GPOWrapper.GetConfiguredMwbAllowServiceModeValue();
 532              _allowServiceModeIsGPOConfigured = _allowServiceModeGpoConfiguration == GpoRuleConfigured.Disabled;
 533              _clipboardSharingEnabledGpoConfiguration = GPOWrapper.GetConfiguredMwbClipboardSharingEnabledValue();
 534              _clipboardSharingEnabledIsGPOConfigured = _clipboardSharingEnabledGpoConfiguration == GpoRuleConfigured.Disabled;
 535              _fileTransferEnabledGpoConfiguration = GPOWrapper.GetConfiguredMwbFileTransferEnabledValue();
 536              _fileTransferEnabledIsGPOConfigured = _fileTransferEnabledGpoConfiguration == GpoRuleConfigured.Disabled;
 537              _useOriginalUserInterfaceGpoConfiguration = GPOWrapper.GetConfiguredMwbUseOriginalUserInterfaceValue();
 538              _useOriginalUserInterfaceIsGPOConfigured = _useOriginalUserInterfaceGpoConfiguration == GpoRuleConfigured.Disabled;
 539  
 540              // Policies supporting enabled and disabled state
 541              _sameSubnetOnlyGpoConfiguration = GPOWrapper.GetConfiguredMwbSameSubnetOnlyValue();
 542              _sameSubnetOnlyIsGPOConfigured = _sameSubnetOnlyGpoConfiguration == GpoRuleConfigured.Enabled || _sameSubnetOnlyGpoConfiguration == GpoRuleConfigured.Disabled;
 543              _validateRemoteIpGpoConfiguration = GPOWrapper.GetConfiguredMwbValidateRemoteIpValue();
 544              _validateRemoteIpIsGPOConfigured = _validateRemoteIpGpoConfiguration == GpoRuleConfigured.Enabled || _validateRemoteIpGpoConfiguration == GpoRuleConfigured.Disabled;
 545  
 546              // Special policies
 547              _policyDefinedIpMappingRulesGPOData = GPOWrapper.GetConfiguredMwbPolicyDefinedIpMappingRules();
 548              _policyDefinedIpMappingRulesIsGPOConfigured = !string.IsNullOrWhiteSpace(_policyDefinedIpMappingRulesGPOData);
 549          }
 550  
 551          public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
 552          {
 553              var hotkeysDict = new Dictionary<string, HotkeySettings[]>
 554              {
 555                  [ModuleName] = [
 556                      ToggleEasyMouseShortcut,
 557                      LockMachinesShortcut,
 558                      HotKeySwitch2AllPC,
 559                      ReconnectShortcut],
 560              };
 561  
 562              return hotkeysDict;
 563          }
 564  
 565          private void LoadViewModelFromSettings(MouseWithoutBordersSettings moduleSettings)
 566          {
 567              ArgumentNullException.ThrowIfNull(moduleSettings);
 568  
 569              Settings = moduleSettings;
 570              /* TODO: Error handling */
 571              _selectedSwitchBetweenMachineShortcutOptionsIndex = Array.IndexOf(_switchBetweenMachineShortcutOptions, moduleSettings.Properties.HotKeySwitchMachine.Value);
 572              _easyMouseOptionIndex = (EasyMouseOption)moduleSettings.Properties.EasyMouse.Value;
 573  
 574              LoadMachineMatrixString();
 575          }
 576  
 577          // Loads the machine matrix, taking into account changes to the machine pool.
 578          private void LoadMachineMatrixString()
 579          {
 580              List<string> loadMachineMatrixString = Settings.Properties.MachineMatrixString ?? new List<string>() { string.Empty, string.Empty, string.Empty, string.Empty };
 581  
 582              if (loadMachineMatrixString.Count < 4)
 583              {
 584                  // Current logic of MWB assumes there are always 4 slots. Any other configuration means data corruption here.
 585                  loadMachineMatrixString = new List<string>() { string.Empty, string.Empty, string.Empty, string.Empty };
 586              }
 587  
 588              bool editedTheMatrix = false; // keep track of changes to the matrix because of changes to the available machine pool.
 589  
 590              if (!string.IsNullOrEmpty(Settings.Properties.MachinePool?.Value))
 591              {
 592                  List<string> availableMachines = new List<string>();
 593  
 594                  // Format of this field is "NAME1:ID1,NAME2:ID2,..."
 595                  // Load the available machines
 596                  foreach (string availableMachineIdPair in Settings.Properties.MachinePool.Value.Split(","))
 597                  {
 598                      string availableMachineName = availableMachineIdPair.Split(':')[0];
 599                      availableMachines.Add(availableMachineName);
 600                  }
 601  
 602                  // Start by removing the machines from the matrix that are no longer available to pick.
 603                  for (int i = 0; i < loadMachineMatrixString.Count; i++)
 604                  {
 605                      if (!availableMachines.Contains(loadMachineMatrixString[i]))
 606                      {
 607                          editedTheMatrix = true;
 608                          loadMachineMatrixString[i] = string.Empty;
 609                      }
 610                  }
 611  
 612                  // If an available machine is not in the matrix already, fill it in the first available spot.
 613                  foreach (string availableMachineName in availableMachines)
 614                  {
 615                      if (!loadMachineMatrixString.Contains(availableMachineName))
 616                      {
 617                          int availableIndex = loadMachineMatrixString.FindIndex(name => string.IsNullOrEmpty(name));
 618                          if (availableIndex >= 0)
 619                          {
 620                              loadMachineMatrixString[availableIndex] = availableMachineName;
 621                              editedTheMatrix = true;
 622                          }
 623                      }
 624                  }
 625              }
 626  
 627              // Dragging while elevated crashes on WinUI3: https://github.com/microsoft/microsoft-ui-xaml/issues/7690
 628              machineMatrixString = new IndexedObservableCollection<DeviceViewModel>(loadMachineMatrixString.Select(name => new DeviceViewModel { Name = name, CanDragDrop = !IsElevated }));
 629  
 630              if (editedTheMatrix)
 631              {
 632                  // Set the property directly to save the new matrix right away with the new available machines.
 633                  MachineMatrixString = machineMatrixString;
 634              }
 635          }
 636  
 637          public bool CanBeEnabled
 638          {
 639              get => !_enabledStateIsGPOConfigured;
 640          }
 641  
 642          public bool CanToggleUseService
 643          {
 644              get
 645              {
 646                  return IsEnabled && !(!IsElevated && !UseService);
 647              }
 648          }
 649  
 650          public bool IsEnabled
 651          {
 652              get => _isEnabled;
 653              set
 654              {
 655                  if (_enabledStateIsGPOConfigured)
 656                  {
 657                      // If it's GPO configured, shouldn't be able to change this state.
 658                      return;
 659                  }
 660  
 661                  if (_isEnabled != value)
 662                  {
 663                      _isEnabled = value;
 664                      GeneralSettingsConfig.Enabled.MouseWithoutBorders = value;
 665                      OnPropertyChanged(nameof(IsEnabled));
 666                      OnPropertyChanged(nameof(ShowInfobarRunAsAdminText));
 667                      OnPropertyChanged(nameof(ShowInfobarCannotDragDropAsAdmin));
 668                      OnPropertyChanged(nameof(ShowPolicyConfiguredInfoForBehaviorSettings));
 669                      OnPropertyChanged(nameof(ShowPolicyConfiguredInfoForName2IPSetting));
 670                      OnPropertyChanged(nameof(ShowPolicyConfiguredInfoForOriginalUiSetting));
 671                      OnPropertyChanged(nameof(Name2IpListPolicyIsConfigured));
 672  
 673                      Task.Run(async () =>
 674                      {
 675                          if (!value)
 676                          {
 677                              try
 678                              {
 679                                  await SubmitShutdownRequestAsync();
 680                              }
 681                              catch (Exception ex)
 682                              {
 683                                  Logger.LogError($"Failed to shutdown MWB via SettingsSync: {ex}");
 684                              }
 685                          }
 686  
 687                          _uiDispatcherQueue.TryEnqueue(DispatcherQueuePriority.Normal, () =>
 688                          {
 689                              OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
 690                              SendConfigMSG(outgoing.ToString());
 691  
 692                              NotifyPropertyChanged();
 693  
 694                              // Disable service mode if we're not elevated, because we cannot register service in that case
 695                              if (value == true && !IsElevated && UseService)
 696                              {
 697                                  UseService = false;
 698                              }
 699                          });
 700                      });
 701                  }
 702              }
 703          }
 704  
 705          public string SecurityKey
 706          {
 707              get => Settings.Properties.SecurityKey.Value;
 708  
 709              set
 710              {
 711                  if (value != Settings.Properties.SecurityKey.Value)
 712                  {
 713                      Settings.Properties.SecurityKey.Value = value;
 714                      NotifyPropertyChanged();
 715                  }
 716              }
 717          }
 718  
 719          public bool WrapMouse
 720          {
 721              get
 722              {
 723                  return Settings.Properties.WrapMouse;
 724              }
 725  
 726              set
 727              {
 728                  if (Settings.Properties.WrapMouse != value)
 729                  {
 730                      Settings.Properties.WrapMouse = value;
 731                      NotifyPropertyChanged();
 732                  }
 733              }
 734          }
 735  
 736          public bool MatrixOneRow
 737          {
 738              get
 739              {
 740                  return Settings.Properties.MatrixOneRow;
 741              }
 742  
 743              set
 744              {
 745                  if (Settings.Properties.MatrixOneRow != value)
 746                  {
 747                      Settings.Properties.MatrixOneRow = value;
 748                      NotifyPropertyChanged();
 749                  }
 750              }
 751          }
 752  
 753          public bool ShareClipboard
 754          {
 755              get
 756              {
 757                  if (_clipboardSharingEnabledGpoConfiguration == GpoRuleConfigured.Disabled)
 758                  {
 759                      return false;
 760                  }
 761  
 762                  return Settings.Properties.ShareClipboard;
 763              }
 764  
 765              set
 766              {
 767                  if (!_clipboardSharingEnabledIsGPOConfigured && (Settings.Properties.ShareClipboard != value))
 768                  {
 769                      Settings.Properties.ShareClipboard = value;
 770                      NotifyPropertyChanged();
 771                      OnPropertyChanged(nameof(TransferFile));
 772                      OnPropertyChanged(nameof(CardForTransferFileSettingIsEnabled));
 773                  }
 774              }
 775          }
 776  
 777          public bool CardForShareClipboardSettingIsEnabled => _clipboardSharingEnabledIsGPOConfigured == false;
 778  
 779          public bool TransferFile
 780          {
 781              get
 782              {
 783                  if (_fileTransferEnabledGpoConfiguration == GpoRuleConfigured.Disabled)
 784                  {
 785                      return false;
 786                  }
 787  
 788                  return Settings.Properties.TransferFile && Settings.Properties.ShareClipboard;
 789              }
 790  
 791              set
 792              {
 793                  // If ShareClipboard is disabled the file transfer does not work and the setting is disabled. => Don't save toggle state.
 794                  // If FileTransferGpo is configured the file transfer does not work and the setting is disabled. => Don't save toggle state.
 795                  if (!ShareClipboard || _fileTransferEnabledIsGPOConfigured)
 796                  {
 797                      return;
 798                  }
 799  
 800                  if (Settings.Properties.TransferFile != value)
 801                  {
 802                      Settings.Properties.TransferFile = value;
 803                      NotifyPropertyChanged();
 804                  }
 805              }
 806          }
 807  
 808          public bool CardForTransferFileSettingIsEnabled
 809          {
 810              get => ShareClipboard && !_fileTransferEnabledIsGPOConfigured;
 811          }
 812  
 813          public bool HideMouseAtScreenEdge
 814          {
 815              get
 816              {
 817                  return Settings.Properties.HideMouseAtScreenEdge;
 818              }
 819  
 820              set
 821              {
 822                  if (Settings.Properties.HideMouseAtScreenEdge != value)
 823                  {
 824                      Settings.Properties.HideMouseAtScreenEdge = value;
 825                      NotifyPropertyChanged();
 826                  }
 827              }
 828          }
 829  
 830          public bool DrawMouseCursor
 831          {
 832              get
 833              {
 834                  return Settings.Properties.DrawMouseCursor;
 835              }
 836  
 837              set
 838              {
 839                  if (Settings.Properties.DrawMouseCursor != value)
 840                  {
 841                      Settings.Properties.DrawMouseCursor = value;
 842                      NotifyPropertyChanged();
 843                  }
 844              }
 845          }
 846  
 847          public bool ValidateRemoteMachineIP
 848          {
 849              get
 850              {
 851                  if (_validateRemoteIpGpoConfiguration == GpoRuleConfigured.Enabled)
 852                  {
 853                      return true;
 854                  }
 855                  else if (_validateRemoteIpGpoConfiguration == GpoRuleConfigured.Disabled)
 856                  {
 857                      return false;
 858                  }
 859  
 860                  return Settings.Properties.ValidateRemoteMachineIP;
 861              }
 862  
 863              set
 864              {
 865                  if (!_validateRemoteIpIsGPOConfigured && (Settings.Properties.ValidateRemoteMachineIP != value))
 866                  {
 867                      Settings.Properties.ValidateRemoteMachineIP = value;
 868                      NotifyPropertyChanged();
 869                  }
 870              }
 871          }
 872  
 873          public bool CardForValidateRemoteIpSettingIsEnabled => _validateRemoteIpIsGPOConfigured == false;
 874  
 875          public string Name2IP
 876          {
 877              // Due to https://github.com/microsoft/microsoft-ui-xaml/issues/1826, we must
 878              // add back \n chars on set and remove them on get for the widget
 879              // to make its behavior consistent with the old UI and MWB internal code.
 880              get
 881              {
 882                  if (_disableUserDefinedIpMappingRulesGpoConfiguration == GpoRuleConfigured.Enabled)
 883                  {
 884                      return string.Empty;
 885                  }
 886  
 887                  return Settings.Properties.Name2IP.Value.Replace("\r\n", "\r");
 888              }
 889  
 890              set
 891              {
 892                  if (_disableUserDefinedIpMappingRulesIsGPOConfigured)
 893                  {
 894                      return;
 895                  }
 896  
 897                  var newValue = value.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n");
 898  
 899                  if (Settings.Properties.Name2IP.Value != newValue)
 900                  {
 901                      Settings.Properties.Name2IP.Value = newValue;
 902                      NotifyPropertyChanged();
 903                  }
 904              }
 905          }
 906  
 907          private string _easyMouseIgnoredFullscreenAppsString;
 908  
 909          public string EasyMouseFullscreenSwitchBlockExcludedApps
 910          {
 911              // Convert the list of excluded apps retrieved from the settings
 912              // to a single string that can be displayed in the bound textbox
 913              get
 914              {
 915                  if (_easyMouseIgnoredFullscreenAppsString == null)
 916                  {
 917                      var excludedApps = Settings.Properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value;
 918                      _easyMouseIgnoredFullscreenAppsString = excludedApps.Count == 0 ? string.Empty : string.Join('\r', excludedApps);
 919                  }
 920  
 921                  return _easyMouseIgnoredFullscreenAppsString;
 922              }
 923  
 924              set
 925              {
 926                  if (EasyMouseFullscreenSwitchBlockExcludedApps == value)
 927                  {
 928                      return;
 929                  }
 930  
 931                  _easyMouseIgnoredFullscreenAppsString = value;
 932  
 933                  var ignoredAppsSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 934                  if (value != string.Empty)
 935                  {
 936                      ignoredAppsSet.UnionWith(value.Split('\r', StringSplitOptions.RemoveEmptyEntries));
 937                  }
 938  
 939                  Settings.Properties.EasyMouseFullscreenSwitchBlockExcludedApps.Value = ignoredAppsSet;
 940                  NotifyPropertyChanged();
 941              }
 942          }
 943  
 944          public bool CardForName2IpSettingIsEnabled => _disableUserDefinedIpMappingRulesIsGPOConfigured == false;
 945  
 946          public bool ShowPolicyConfiguredInfoForName2IPSetting => _disableUserDefinedIpMappingRulesIsGPOConfigured && IsEnabled;
 947  
 948          public string Name2IpListPolicyData
 949          {
 950              // Due to https://github.com/microsoft/microsoft-ui-xaml/issues/1826, we must
 951              // add back \n chars on set and remove them on get for the widget
 952              // to make its behavior consistent with the old UI and MWB internal code.
 953              // get => GPOWrapper.GetConfiguredMwbPolicyDefinedIpMappingRules().Replace("\r\n", "\r");
 954              get => _policyDefinedIpMappingRulesGPOData.Replace("\r\n", "\r");
 955          }
 956  
 957          public bool Name2IpListPolicyIsConfigured => _policyDefinedIpMappingRulesIsGPOConfigured && IsEnabled;
 958  
 959          public bool SameSubnetOnly
 960          {
 961              get
 962              {
 963                  if (_sameSubnetOnlyGpoConfiguration == GpoRuleConfigured.Enabled)
 964                  {
 965                      return true;
 966                  }
 967                  else if (_sameSubnetOnlyGpoConfiguration == GpoRuleConfigured.Disabled)
 968                  {
 969                      return false;
 970                  }
 971  
 972                  return Settings.Properties.SameSubnetOnly;
 973              }
 974  
 975              set
 976              {
 977                  if (!_sameSubnetOnlyIsGPOConfigured && (Settings.Properties.SameSubnetOnly != value))
 978                  {
 979                      Settings.Properties.SameSubnetOnly = value;
 980                      NotifyPropertyChanged();
 981                  }
 982              }
 983          }
 984  
 985          public bool CardForSameSubnetOnlySettingIsEnabled => _sameSubnetOnlyIsGPOConfigured == false;
 986  
 987          public bool BlockScreenSaverOnOtherMachines
 988          {
 989              get
 990              {
 991                  if (_disallowBlockingScreensaverGpoConfiguration == GpoRuleConfigured.Enabled)
 992                  {
 993                      return false;
 994                  }
 995  
 996                  return Settings.Properties.BlockScreenSaverOnOtherMachines;
 997              }
 998  
 999              set
1000              {
1001                  if (_disallowBlockingScreensaverIsGPOConfigured)
1002                  {
1003                      return;
1004                  }
1005  
1006                  if (Settings.Properties.BlockScreenSaverOnOtherMachines != value)
1007                  {
1008                      Settings.Properties.BlockScreenSaverOnOtherMachines = value;
1009                      NotifyPropertyChanged();
1010                  }
1011              }
1012          }
1013  
1014          public bool CardForBlockScreensaverSettingIsEnabled => _disallowBlockingScreensaverIsGPOConfigured == false;
1015  
1016          // Should match EasyMouseOption enum from MouseWithoutBorders and the ComboBox in the MouseWithoutBordersView.cs
1017          private enum EasyMouseOption
1018          {
1019              Disable = 0,
1020              Enable = 1,
1021              Ctrl = 2,
1022              Shift = 3,
1023          }
1024  
1025          private EasyMouseOption _easyMouseOptionIndex;
1026  
1027          public int EasyMouseOptionIndex
1028          {
1029              get
1030              {
1031                  return (int)_easyMouseOptionIndex;
1032              }
1033  
1034              set
1035              {
1036                  if (value != (int)_easyMouseOptionIndex)
1037                  {
1038                      _easyMouseOptionIndex = (EasyMouseOption)value;
1039                      Settings.Properties.EasyMouse.Value = value;
1040                      NotifyPropertyChanged(nameof(EasyMouseOptionIndex));
1041                  }
1042              }
1043          }
1044  
1045          public bool EasyMouseEnabled => (EasyMouseOption)EasyMouseOptionIndex != EasyMouseOption.Disable;
1046  
1047          public bool IsEasyMouseBlockingOnFullscreenEnabled =>
1048              EasyMouseEnabled && DisableEasyMouseWhenForegroundWindowIsFullscreen;
1049  
1050          public bool DisableEasyMouseWhenForegroundWindowIsFullscreen
1051          {
1052              get
1053              {
1054                  return Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen;
1055              }
1056  
1057              set
1058              {
1059                  if (Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen == value)
1060                  {
1061                      return;
1062                  }
1063  
1064                  Settings.Properties.DisableEasyMouseWhenForegroundWindowIsFullscreen = value;
1065                  NotifyPropertyChanged();
1066              }
1067          }
1068  
1069          public HotkeySettings ToggleEasyMouseShortcut
1070          {
1071              get => Settings.Properties.ToggleEasyMouseShortcut;
1072  
1073              set
1074              {
1075                  if (Settings.Properties.ToggleEasyMouseShortcut != value)
1076                  {
1077                      Settings.Properties.ToggleEasyMouseShortcut = value ?? MouseWithoutBordersProperties.DefaultHotKeyToggleEasyMouse;
1078                      NotifyPropertyChanged();
1079                      NotifyModuleUpdatedSettings();
1080                  }
1081              }
1082          }
1083  
1084          public HotkeySettings LockMachinesShortcut
1085          {
1086              get => Settings.Properties.LockMachineShortcut;
1087  
1088              set
1089              {
1090                  if (Settings.Properties.LockMachineShortcut != value)
1091                  {
1092                      Settings.Properties.LockMachineShortcut = value;
1093                      Settings.Properties.LockMachineShortcut = value ?? MouseWithoutBordersProperties.DefaultHotKeyLockMachine;
1094                      NotifyPropertyChanged();
1095                      NotifyModuleUpdatedSettings();
1096                  }
1097              }
1098          }
1099  
1100          public HotkeySettings ReconnectShortcut
1101          {
1102              get => Settings.Properties.ReconnectShortcut;
1103  
1104              set
1105              {
1106                  if (Settings.Properties.ReconnectShortcut != value)
1107                  {
1108                      Settings.Properties.ReconnectShortcut = value;
1109                      Settings.Properties.ReconnectShortcut = value ?? MouseWithoutBordersProperties.DefaultHotKeyReconnect;
1110                      NotifyPropertyChanged();
1111                      NotifyModuleUpdatedSettings();
1112                  }
1113              }
1114          }
1115  
1116          public HotkeySettings HotKeySwitch2AllPC
1117          {
1118              get => Settings.Properties.Switch2AllPCShortcut;
1119  
1120              set
1121              {
1122                  if (Settings.Properties.Switch2AllPCShortcut != value)
1123                  {
1124                      Settings.Properties.Switch2AllPCShortcut = value;
1125                      Settings.Properties.Switch2AllPCShortcut = value ?? MouseWithoutBordersProperties.DefaultHotKeySwitch2AllPC;
1126                      NotifyPropertyChanged();
1127                      NotifyModuleUpdatedSettings();
1128                  }
1129              }
1130          }
1131  
1132          private int _selectedSwitchBetweenMachineShortcutOptionsIndex;
1133  
1134          public int SelectedSwitchBetweenMachineShortcutOptionsIndex
1135          {
1136              get
1137              {
1138                  return _selectedSwitchBetweenMachineShortcutOptionsIndex;
1139              }
1140  
1141              set
1142              {
1143                  if (_selectedSwitchBetweenMachineShortcutOptionsIndex != value)
1144                  {
1145                      _selectedSwitchBetweenMachineShortcutOptionsIndex = value;
1146                      Settings.Properties.HotKeySwitchMachine.Value = _switchBetweenMachineShortcutOptions[value];
1147                      NotifyPropertyChanged();
1148                  }
1149              }
1150          }
1151  
1152          public bool MoveMouseRelatively
1153          {
1154              get
1155              {
1156                  return Settings.Properties.MoveMouseRelatively;
1157              }
1158  
1159              set
1160              {
1161                  if (Settings.Properties.MoveMouseRelatively != value)
1162                  {
1163                      Settings.Properties.MoveMouseRelatively = value;
1164                      NotifyPropertyChanged();
1165                  }
1166              }
1167          }
1168  
1169          public bool BlockMouseAtScreenCorners
1170          {
1171              get
1172              {
1173                  return Settings.Properties.BlockMouseAtScreenCorners;
1174              }
1175  
1176              set
1177              {
1178                  if (Settings.Properties.BlockMouseAtScreenCorners != value)
1179                  {
1180                      Settings.Properties.BlockMouseAtScreenCorners = value;
1181                      NotifyPropertyChanged();
1182                  }
1183              }
1184          }
1185  
1186          private IndexedObservableCollection<DeviceViewModel> machineMatrixString;
1187  
1188          public partial class DeviceViewModel : Observable
1189          {
1190              public string Name { get; set; }
1191  
1192              public bool CanDragDrop { get; set; }
1193  
1194              private Brush _statusBrush = StatusColors[SocketStatus.NA];
1195  
1196              public Brush StatusBrush
1197              {
1198                  get
1199                  {
1200                      return _statusBrush;
1201                  }
1202  
1203                  set
1204                  {
1205                      if (_statusBrush != value)
1206                      {
1207                          _statusBrush = value;
1208                          OnPropertyChanged(nameof(StatusBrush));
1209                      }
1210                  }
1211              }
1212          }
1213  
1214          public IndexedObservableCollection<DeviceViewModel> MachineMatrixString
1215          {
1216              get
1217              {
1218                  lock (_machineMatrixStringLock)
1219                  {
1220                      return machineMatrixString;
1221                  }
1222              }
1223  
1224              set
1225              {
1226                  lock (_machineMatrixStringLock)
1227                  {
1228                      machineMatrixString = value;
1229                  }
1230  
1231                  Settings.Properties.MachineMatrixString = new List<string>(value.ToEnumerable().Select(d => d.Name));
1232                  NotifyPropertyChanged();
1233              }
1234          }
1235  
1236          public bool ShowClipboardAndNetworkStatusMessages
1237          {
1238              get
1239              {
1240                  return Settings.Properties.ShowClipboardAndNetworkStatusMessages;
1241              }
1242  
1243              set
1244              {
1245                  if (Settings.Properties.ShowClipboardAndNetworkStatusMessages != value)
1246                  {
1247                      Settings.Properties.ShowClipboardAndNetworkStatusMessages = value;
1248                      NotifyPropertyChanged();
1249                  }
1250              }
1251          }
1252  
1253          public bool LoadUpdatedSettings()
1254          {
1255              try
1256              {
1257                  LoadViewModelFromSettings(SettingsUtils.GetSettings<MouseWithoutBordersSettings>("MouseWithoutBorders"));
1258                  return true;
1259              }
1260              catch (System.Exception ex)
1261              {
1262                  Logger.LogError(ex.Message);
1263                  return false;
1264              }
1265          }
1266  
1267          private void SendCustomAction(string actionName)
1268          {
1269              SendConfigMSG("{\"action\":{\"MouseWithoutBorders\":{\"action_name\":\"" + actionName + "\", \"value\":\"\"}}}");
1270          }
1271  
1272          public void AddFirewallRule()
1273          {
1274              SendCustomAction("add_firewall");
1275          }
1276  
1277          public void RefreshEnabledState()
1278          {
1279              InitializeEnabledValue();
1280              OnPropertyChanged(nameof(IsEnabled));
1281          }
1282  
1283          private void NotifyModuleUpdatedSettings()
1284          {
1285              SendConfigMSG(
1286                  string.Format(
1287                  CultureInfo.InvariantCulture,
1288                  "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
1289                  MouseWithoutBordersSettings.ModuleName,
1290                  JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.MouseWithoutBordersSettings)));
1291          }
1292  
1293          public void NotifyUpdatedSettings()
1294          {
1295              OnPropertyChanged(null); // Notify all properties might have changed.
1296          }
1297  
1298          public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
1299          {
1300              OnPropertyChanged(propertyName);
1301  
1302              // Skip saving settings for UI properties
1303              if (propertyName == nameof(ShowInfobarCannotDragDropAsAdmin) ||
1304                  propertyName == nameof(ShowInfobarRunAsAdminText))
1305              {
1306                  return;
1307              }
1308  
1309              SettingsUtils.SaveSettings(Settings.ToJsonString(), MouseWithoutBordersSettings.ModuleName);
1310  
1311              if (propertyName == nameof(UseService))
1312              {
1313                  NotifyModuleUpdatedSettings();
1314              }
1315          }
1316  
1317          private Func<string, int> SendConfigMSG { get; }
1318  
1319          public void CopyMachineNameToClipboard()
1320          {
1321              var data = new DataPackage();
1322              data.SetText(Dns.GetHostName());
1323              Clipboard.SetContent(data);
1324          }
1325  
1326          protected override void Dispose(bool disposing)
1327          {
1328              if (!_disposed)
1329              {
1330                  if (disposing)
1331                  {
1332                      // Cancel the cancellation token source
1333                      _cancellationTokenSource?.Cancel();
1334                      _cancellationTokenSource?.Dispose();
1335  
1336                      // Wait for the machine polling task to complete
1337                      try
1338                      {
1339                          _machinePollingThreadTask?.Wait(TimeSpan.FromSeconds(1));
1340                      }
1341                      catch (AggregateException)
1342                      {
1343                          // Task was cancelled, which is expected
1344                      }
1345  
1346                      // Dispose the named pipe stream
1347                      try
1348                      {
1349                          syncHelperStream?.Dispose();
1350                      }
1351                      catch (Exception ex)
1352                      {
1353                          Logger.LogError($"Error disposing sync helper stream: {ex}");
1354                      }
1355                      finally
1356                      {
1357                          syncHelperStream = null;
1358                      }
1359  
1360                      // Dispose the semaphore
1361                      _ipcSemaphore?.Dispose();
1362                  }
1363  
1364                  _disposed = true;
1365              }
1366  
1367              base.Dispose(disposing);
1368          }
1369  
1370          internal void UninstallService()
1371          {
1372              SendCustomAction("uninstall_service");
1373          }
1374  
1375          public bool ShowPolicyConfiguredInfoForServiceSettings
1376          {
1377              get
1378              {
1379                  return IsEnabled && _allowServiceModeIsGPOConfigured;
1380              }
1381          }
1382  
1383          public bool ShowPolicyConfiguredInfoForBehaviorSettings
1384          {
1385              get
1386              {
1387                  return IsEnabled && (_disallowBlockingScreensaverIsGPOConfigured
1388                      || _clipboardSharingEnabledIsGPOConfigured || _fileTransferEnabledIsGPOConfigured
1389                      || _sameSubnetOnlyIsGPOConfigured || _validateRemoteIpIsGPOConfigured);
1390              }
1391          }
1392  
1393          public bool ShowInfobarCannotDragDropAsAdmin
1394          {
1395              get { return IsElevated && IsEnabled; }
1396          }
1397  
1398          public bool ShowInfobarRunAsAdminText
1399          {
1400              get { return !CanToggleUseService && IsEnabled && !ShowPolicyConfiguredInfoForServiceSettings; }
1401          }
1402      }
1403  }