/ src / modules / cmdpal / Microsoft.CmdPal.UI / PowerToysRootPageService.cs
PowerToysRootPageService.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.Runtime.InteropServices;
  6  using System.Runtime.Versioning;
  7  using ManagedCommon;
  8  using Microsoft.CmdPal.Core.Common.Services;
  9  using Microsoft.CmdPal.Core.ViewModels;
 10  using Microsoft.CmdPal.UI.ViewModels;
 11  using Microsoft.CmdPal.UI.ViewModels.MainPage;
 12  using Microsoft.CommandPalette.Extensions;
 13  using WinRT;
 14  
 15  // To learn more about WinUI, the WinUI project structure,
 16  // and more about our project templates, see: http://aka.ms/winui-project-info.
 17  namespace Microsoft.CmdPal.UI;
 18  
 19  internal sealed class PowerToysRootPageService : IRootPageService
 20  {
 21      private readonly TopLevelCommandManager _tlcManager;
 22  
 23      private IExtensionWrapper? _activeExtension;
 24      private Lazy<MainListPage> _mainListPage;
 25  
 26      public PowerToysRootPageService(TopLevelCommandManager topLevelCommandManager, SettingsModel settings, AliasManager aliasManager, AppStateModel appStateModel)
 27      {
 28          _tlcManager = topLevelCommandManager;
 29  
 30          _mainListPage = new Lazy<MainListPage>(() =>
 31          {
 32              return new MainListPage(_tlcManager, settings, aliasManager, appStateModel);
 33          });
 34      }
 35  
 36      public async Task PreLoadAsync()
 37      {
 38          await _tlcManager.LoadBuiltinsAsync();
 39      }
 40  
 41      public Microsoft.CommandPalette.Extensions.IPage GetRootPage()
 42      {
 43          return _mainListPage.Value;
 44      }
 45  
 46      public async Task PostLoadRootPageAsync()
 47      {
 48          // After loading built-ins, and starting navigation, kick off a thread to load extensions.
 49          _tlcManager.LoadExtensionsCommand.Execute(null);
 50  
 51          await _tlcManager.LoadExtensionsCommand.ExecutionTask!;
 52          if (_tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
 53          {
 54              // TODO: Handle failure case
 55          }
 56      }
 57  
 58      private void OnPerformTopLevelCommand(object? context)
 59      {
 60          try
 61          {
 62              if (context is IListItem listItem)
 63              {
 64                  _mainListPage.Value.UpdateHistory(listItem);
 65              }
 66          }
 67          catch (Exception ex)
 68          {
 69              Logger.LogError("Failed to update history in PowerToysRootPageService");
 70              Logger.LogError(ex.ToString());
 71          }
 72      }
 73  
 74      public void OnPerformCommand(object? context, bool topLevel, AppExtensionHost? currentHost)
 75      {
 76          if (topLevel)
 77          {
 78              OnPerformTopLevelCommand(context);
 79          }
 80  
 81          if (currentHost is CommandPaletteHost host)
 82          {
 83              SetActiveExtension(host.Extension);
 84          }
 85          else
 86          {
 87              throw new InvalidOperationException("This must be a programming error - everything in Command Palette should have a CommandPaletteHost");
 88          }
 89      }
 90  
 91      public void SetActiveExtension(IExtensionWrapper? extension)
 92      {
 93          if (extension != _activeExtension)
 94          {
 95              // There's not really a CoDisallowSetForegroundWindow, so we don't
 96              // need to handle that
 97              _activeExtension = extension;
 98  
 99              var extensionWinRtObject = _activeExtension?.GetExtensionObject();
100              if (extensionWinRtObject is not null)
101              {
102                  try
103                  {
104                      unsafe
105                      {
106                          var winrtObj = (IWinRTObject)extensionWinRtObject;
107                          var intPtr = winrtObj.NativeObject.ThisPtr;
108                          var hr = Native.CoAllowSetForegroundWindow(intPtr);
109                          if (hr != 0)
110                          {
111                              Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}");
112                          }
113                      }
114                  }
115                  catch (Exception ex)
116                  {
117                      ManagedCommon.Logger.LogError(ex.ToString());
118                  }
119              }
120          }
121      }
122  
123      public void GoHome()
124      {
125          SetActiveExtension(null);
126      }
127  
128      // You may ask yourself, why aren't we using CsWin32 for this?
129      // The CsWin32 projected version includes some object marshalling, like so:
130      //
131      // HRESULT CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk,...)
132      //
133      // And if you do it like that, then the IForegroundTransfer interface isn't marshalled correctly
134      internal sealed class Native
135      {
136          [DllImport("OLE32.dll", ExactSpelling = true)]
137          [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
138          [SupportedOSPlatform("windows5.0")]
139          internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
140      }
141  }