Main.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.Diagnostics;
  6  using System.Diagnostics.CodeAnalysis;
  7  using System.Runtime.InteropServices;
  8  
  9  using LazyCache;
 10  using ManagedCommon;
 11  using Microsoft.PowerToys.Run.Plugin.OneNote.Properties;
 12  using ScipBe.Common.Office.OneNote;
 13  using Windows.Win32;
 14  using Windows.Win32.Foundation;
 15  using Windows.Win32.UI.WindowsAndMessaging;
 16  using Wox.Plugin;
 17  
 18  namespace Microsoft.PowerToys.Run.Plugin.OneNote
 19  {
 20      /// <summary>
 21      /// A power launcher plugin to search across time zones.
 22      /// </summary>
 23      public class Main : IPlugin, IDelayedExecutionPlugin, IPluginI18n
 24      {
 25          /// <summary>
 26          /// A value indicating if the OneNote interop library was able to successfully initialize.
 27          /// </summary>
 28          private bool _oneNoteInstalled;
 29  
 30          /// <summary>
 31          /// LazyCache CachingService instance to speed up repeated queries.
 32          /// </summary>
 33          private CachingService? _cache;
 34  
 35          /// <summary>
 36          /// The initial context for this plugin (contains API and meta-data)
 37          /// </summary>
 38          private PluginInitContext? _context;
 39  
 40          /// <summary>
 41          /// The path to the icon for each result
 42          /// </summary>
 43          private string _iconPath;
 44  
 45          /// <summary>
 46          /// Initializes a new instance of the <see cref="Main"/> class.
 47          /// </summary>
 48          public Main()
 49          {
 50              UpdateIconPath(Theme.Light);
 51          }
 52  
 53          /// <summary>
 54          /// Gets the localized name.
 55          /// </summary>
 56          public string Name => Resources.PluginTitle;
 57  
 58          /// <summary>
 59          /// Gets the localized description.
 60          /// </summary>
 61          public string Description => Resources.PluginDescription;
 62  
 63          /// <summary>
 64          /// Gets the plugin ID for validation
 65          /// </summary>
 66          public static string PluginID => "0778F0C264114FEC8A3DF59447CF0A74";
 67  
 68          /// <summary>
 69          /// Initialize the plugin with the given <see cref="PluginInitContext"/>
 70          /// </summary>
 71          /// <param name="context">The <see cref="PluginInitContext"/> for this plugin</param>
 72          public void Init(PluginInitContext context)
 73          {
 74              _context = context ?? throw new ArgumentNullException(nameof(context));
 75  
 76              try
 77              {
 78                  _ = OneNoteProvider.PageItems.Any();
 79                  _oneNoteInstalled = true;
 80  
 81                  _cache = new CachingService();
 82                  _cache.DefaultCachePolicy.DefaultCacheDurationSeconds = (int)TimeSpan.FromDays(1).TotalSeconds;
 83              }
 84              catch (COMException)
 85              {
 86                  // OneNote isn't installed, plugin won't do anything.
 87                  _oneNoteInstalled = false;
 88              }
 89  
 90              _context.API.ThemeChanged += OnThemeChanged;
 91              UpdateIconPath(_context.API.GetCurrentTheme());
 92          }
 93  
 94          /// <summary>
 95          /// Return a filtered list, based on the given query
 96          /// </summary>
 97          /// <param name="query">The query to filter the list</param>
 98          /// <returns>A filtered list, can be empty when nothing was found</returns>
 99          public List<Result> Query(Query query)
100          {
101              if (!_oneNoteInstalled || query is null || string.IsNullOrWhiteSpace(query.Search) || _cache is null)
102              {
103                  return new List<Result>(0);
104              }
105  
106              // If there's cached results for this query, return immediately; otherwise, wait for delayedExecution.
107              var results = _cache.Get<List<Result>>(query.Search);
108              return results ?? Query(query, false);
109          }
110  
111          /// <summary>
112          /// Return a filtered list, based on the given query
113          /// </summary>
114          /// <param name="query">The query to filter the list</param>
115          /// <param name="delayedExecution">False if this is the first pass through plugins, true otherwise. Slow plugins should run delayed.</param>
116          /// <returns>A filtered list, can be empty when nothing was found</returns>
117          public List<Result> Query(Query query, bool delayedExecution)
118          {
119              if (!delayedExecution || !_oneNoteInstalled || query is null || string.IsNullOrWhiteSpace(query.Search) || _cache is null)
120              {
121                  return new List<Result>(0);
122              }
123  
124              // Get results from cache if they already exist for this query; otherwise, query OneNote. Results will be cached for 1 day.
125              var results = _cache.GetOrAdd(query.Search, () =>
126              {
127                  var pages = OneNoteProvider.FindPages(query.Search);
128  
129                  return pages.Select(p => new Result
130                  {
131                      IcoPath = _iconPath,
132                      Title = p.Name,
133                      QueryTextDisplay = p.Name,
134                      SubTitle = @$"{p.Notebook.Name}\{p.Section.Name}",
135                      Action = (_) => OpenPageInOneNote(p),
136                      ContextData = p,
137                      ToolTipData = new ToolTipData(Name, @$"{p.Notebook.Name}\{p.Section.Name}\{p.Name}"),
138                  }).ToList();
139              });
140  
141              return results;
142          }
143  
144          /// <summary>
145          /// Return the translated plugin title.
146          /// </summary>
147          /// <returns>A translated plugin title.</returns>
148          public string GetTranslatedPluginTitle() => Resources.PluginTitle;
149  
150          /// <summary>
151          /// Return the translated plugin description.
152          /// </summary>
153          /// <returns>A translated plugin description.</returns>
154          public string GetTranslatedPluginDescription() => Resources.PluginDescription;
155  
156          private void OnThemeChanged(Theme currentTheme, Theme newTheme)
157          {
158              UpdateIconPath(newTheme);
159          }
160  
161          [MemberNotNull(nameof(_iconPath))]
162          private void UpdateIconPath(Theme theme)
163          {
164              _iconPath = theme == Theme.Light || theme == Theme.HighContrastWhite ? "Images/oneNote.light.png" : "Images/oneNote.dark.png";
165          }
166  
167          private bool OpenPageInOneNote(IOneNoteExtPage page)
168          {
169              try
170              {
171                  page.OpenInOneNote();
172                  ShowOneNote();
173                  return true;
174              }
175              catch (COMException)
176              {
177                  // The page, section or even notebook may no longer exist, ignore and do nothing.
178                  return false;
179              }
180          }
181  
182          /// <summary>
183          /// Brings OneNote to the foreground and restores it if minimized.
184          /// </summary>
185          private void ShowOneNote()
186          {
187              using var process = Process.GetProcessesByName("onenote").FirstOrDefault();
188              if (process?.MainWindowHandle != null)
189              {
190                  HWND handle = (HWND)process.MainWindowHandle;
191                  if (PInvoke.IsIconic(handle))
192                  {
193                      PInvoke.ShowWindow(handle, SHOW_WINDOW_CMD.SW_RESTORE);
194                  }
195  
196                  PInvoke.SetForegroundWindow(handle);
197              }
198          }
199      }
200  }