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;
  6  using System.Collections.Generic;
  7  using System.Globalization;
  8  using System.Linq;
  9  using System.Text;
 10  using System.Windows.Controls;
 11  
 12  using ManagedCommon;
 13  using Microsoft.PowerToys.Settings.UI.Library;
 14  using Wox.Infrastructure;
 15  using Wox.Plugin;
 16  using Wox.Plugin.Logger;
 17  
 18  using BrowserInfo = Wox.Plugin.Common.DefaultBrowserInfo;
 19  
 20  namespace Community.PowerToys.Run.Plugin.WebSearch
 21  {
 22      public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider, IReloadable, IDisposable
 23      {
 24          // Should only be set in Init()
 25          private Action onPluginError;
 26  
 27          private const string NotGlobalIfUri = nameof(NotGlobalIfUri);
 28  
 29          /// <summary>If true, dont show global result on queries that are URIs</summary>
 30          private bool _notGlobalIfUri;
 31  
 32          private PluginInitContext _context;
 33  
 34          private string _iconPath;
 35  
 36          private bool _disposed;
 37  
 38          public string Name => Properties.Resources.plugin_name;
 39  
 40          public string Description => Properties.Resources.plugin_description;
 41  
 42          public static string PluginID => "9F1B49201C3F4BF781CAAD5CD88EA4DC";
 43  
 44          private static readonly CompositeFormat PluginInBrowserName = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_in_browser_name);
 45          private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
 46          private static readonly CompositeFormat PluginSearchFailed = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_search_failed);
 47  
 48          public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>()
 49          {
 50              new PluginAdditionalOption()
 51              {
 52                  Key = NotGlobalIfUri,
 53                  DisplayLabel = Properties.Resources.plugin_global_if_uri,
 54                  Value = false,
 55              },
 56          };
 57  
 58          public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
 59          {
 60              return new List<ContextMenuResult>(0);
 61          }
 62  
 63          public List<Result> Query(Query query)
 64          {
 65              ArgumentNullException.ThrowIfNull(query);
 66  
 67              var results = new List<Result>();
 68  
 69              // empty query
 70              if (string.IsNullOrEmpty(query.Search))
 71              {
 72                  string arguments = "? ";
 73                  results.Add(new Result
 74                  {
 75                      Title = Properties.Resources.plugin_description,
 76                      SubTitle = string.Format(CultureInfo.CurrentCulture, PluginInBrowserName, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
 77                      QueryTextDisplay = string.Empty,
 78                      IcoPath = _iconPath,
 79                      ProgramArguments = arguments,
 80                      Action = action =>
 81                      {
 82                          if (!Helper.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, arguments))
 83                          {
 84                              onPluginError();
 85                              return false;
 86                          }
 87  
 88                          return true;
 89                      },
 90                  });
 91                  return results;
 92              }
 93              else
 94              {
 95                  string searchTerm = query.Search;
 96  
 97                  // Don't include in global results if the query is a URI (and if the option NotGlobalIfUri is enabled)
 98                  if (_notGlobalIfUri
 99                      && AreResultsGlobal()
100                      && IsURI(searchTerm))
101                  {
102                      return results;
103                  }
104  
105                  var result = new Result
106                  {
107                      Title = searchTerm,
108                      SubTitle = string.Format(CultureInfo.CurrentCulture, PluginOpen, BrowserInfo.Name ?? BrowserInfo.MSEdgeName),
109                      QueryTextDisplay = searchTerm,
110                      IcoPath = _iconPath,
111                  };
112  
113                  string arguments = $"? {searchTerm}";
114  
115                  result.ProgramArguments = arguments;
116                  result.Action = action =>
117                  {
118                      if (!Helper.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, arguments))
119                      {
120                          onPluginError();
121                          return false;
122                      }
123  
124                      return true;
125                  };
126  
127                  results.Add(result);
128              }
129  
130              return results;
131  
132              bool AreResultsGlobal()
133              {
134                  return string.IsNullOrEmpty(query.ActionKeyword);
135              }
136  
137              // Checks if input is a URI the same way Microsoft.Plugin.Uri.UriHelper.ExtendedUriParser does
138              bool IsURI(string input)
139              {
140                  if (input.EndsWith(":", StringComparison.OrdinalIgnoreCase)
141                      && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase)
142                      && !input.Contains('/', StringComparison.OrdinalIgnoreCase)
143                      && !input.All(char.IsDigit)
144                      && System.Text.RegularExpressions.Regex.IsMatch(input, @"^([a-z][a-z0-9+\-.]*):"))
145                  {
146                      return true;
147                  }
148  
149                  if (input.EndsWith(":", StringComparison.CurrentCulture)
150                      || input.EndsWith(".", StringComparison.CurrentCulture)
151                      || input.EndsWith(":/", StringComparison.CurrentCulture)
152                      || input.EndsWith("://", StringComparison.CurrentCulture)
153                      || input.All(char.IsDigit))
154                  {
155                      return false;
156                  }
157  
158                  try
159                  {
160                      _ = new UriBuilder(input);
161                  }
162                  catch (UriFormatException)
163                  {
164                      return false;
165                  }
166  
167                  return true;
168              }
169          }
170  
171          public void Init(PluginInitContext context)
172          {
173              _context = context ?? throw new ArgumentNullException(nameof(context));
174              _context.API.ThemeChanged += OnThemeChanged;
175              UpdateIconPath(_context.API.GetCurrentTheme());
176              BrowserInfo.UpdateIfTimePassed();
177  
178              onPluginError = () =>
179              {
180                  string errorMsgString = string.Format(CultureInfo.CurrentCulture, PluginSearchFailed, BrowserInfo.Name ?? BrowserInfo.MSEdgeName);
181  
182                  Log.Error(errorMsgString, this.GetType());
183                  _context.API.ShowMsg(
184                      $"Plugin: {Properties.Resources.plugin_name}",
185                      errorMsgString);
186              };
187          }
188  
189          public string GetTranslatedPluginTitle()
190          {
191              return Properties.Resources.plugin_name;
192          }
193  
194          public string GetTranslatedPluginDescription()
195          {
196              return Properties.Resources.plugin_description;
197          }
198  
199          private void OnThemeChanged(Theme oldtheme, Theme newTheme)
200          {
201              UpdateIconPath(newTheme);
202          }
203  
204          private void UpdateIconPath(Theme theme)
205          {
206              if (theme == Theme.Light || theme == Theme.HighContrastWhite)
207              {
208                  _iconPath = "Images/WebSearch.light.png";
209              }
210              else
211              {
212                  _iconPath = "Images/WebSearch.dark.png";
213              }
214          }
215  
216          public Control CreateSettingPanel()
217          {
218              throw new NotImplementedException();
219          }
220  
221          public void UpdateSettings(PowerLauncherPluginSettings settings)
222          {
223              _notGlobalIfUri = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == NotGlobalIfUri)?.Value ?? false;
224          }
225  
226          public void ReloadData()
227          {
228              if (_context is null)
229              {
230                  return;
231              }
232  
233              UpdateIconPath(_context.API.GetCurrentTheme());
234              BrowserInfo.UpdateIfTimePassed();
235          }
236  
237          public void Dispose()
238          {
239              Dispose(true);
240              GC.SuppressFinalize(this);
241          }
242  
243          protected virtual void Dispose(bool disposing)
244          {
245              if (!_disposed && disposing)
246              {
247                  if (_context != null && _context.API != null)
248                  {
249                      _context.API.ThemeChanged -= OnThemeChanged;
250                  }
251  
252                  _disposed = true;
253              }
254          }
255      }
256  }