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 }