[PT Run][New Plugin] Web Search (#14882)

* [PT Run][New Plugin] Web Search

* [PT Run][Web Search] Added plugin files to WXS and YML

* [PT Run][Web Search] Added docs

* [PT Run][Web Search] Added new option + minor modifications

* [PT Run][Web Search] Fixed a bug + minor refactoring

* [PT Run][Web Search] Updated docs and changed icons

* [PT Run][Web Search] Fixed capitalization for spell check + fixed icons

* [PT Run][Web Search] Fixed renaming

* adding cyberrex to name

* [PT Run][Web Search] Fixed typo in doc screenshot

* [PT Run][Web Search] Fixed plugin not working with Opera browser + minor modifications

* Fixed merge and typo

* [PT Run][Web Search] Removed globalization error suppression + changed action word

* [PT Run][Web Search] Added LocProject.json + minor modifications

* [PT Run][Web Search] Using plugin icon to not confuse with uri plugin + changed action word because another plugin uses it

* Added Firefox to spell-check expect.txt

* [PT Run][Web Search] Fixed bug with opera + changed default setting's value to false

* Added sourceid to spell-check expect.txt

* [PT Run][WebSearch] Changed action word

* Making Web plugin a dependency for launcher like other plugins

* [PT Run][Web Search] Now using JsonDocument + better way of getting browser name

* [PT Run][Web Search] Fixed bug

* adding in DLL to signing for release.yml build

Co-authored-by: Clint Rutkas <clint@rutkas.com>
This commit is contained in:
cyberrex5
2021-12-24 04:56:44 +02:00
committed by GitHub
parent 7d0304fd06
commit bb88aff663
17 changed files with 894 additions and 1 deletions

View File

@@ -347,6 +347,7 @@ cxfksword
CXSMICON
CXVIRTUALSCREEN
cxxopts
cyberrex
CYSMICON
CYVIRTUALSCREEN
czf
@@ -589,6 +590,7 @@ Filterx
finalizer
findfast
findstr
Firefox
FIXEDFILEINFO
FLASHZONES
FLASHZONESONQUICKSWITCH
@@ -1796,6 +1798,7 @@ somil
Soref
SOURCECLIENTAREAONLY
SOURCEHEADER
sourceid
sourcesdirectory
spamming
spdisp

View File

@@ -88,6 +88,7 @@
"modules\\launcher\\Plugins\\VSCodeWorkspaces\\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll",
"modules\\launcher\\Plugins\\Service\\Microsoft.PowerToys.Run.Plugin.Service.dll",
"modules\\launcher\\Plugins\\System\\Microsoft.PowerToys.Run.Plugin.System.dll",
"modules\\launcher\\Plugins\\WebSearch\\Community.PowerToys.Run.Plugin.WebSearch.dll",
"modules\\launcher\\Plugins\\WindowsTerminal\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll",
"modules\\MouseUtils\\PowerToys.FindMyMouse.dll",

View File

@@ -161,6 +161,7 @@ build:
- 'modules\launcher\Plugins\WindowWalker\PowerToys.ManagedTelemetry.dll'
- 'modules\launcher\Plugins\UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.dll'
- 'modules\launcher\Plugins\VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll'
- 'modules\launcher\Plugins\WebSearch\Community.PowerToys.Run.Plugin.WebSearch.dll'
- 'modules\launcher\Plugins\Service\Microsoft.PowerToys.Run.Plugin.Service.dll'
- 'modules\launcher\Plugins\System\Microsoft.PowerToys.Run.Plugin.System.dll'
- 'modules\launcher\Plugins\WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll'

View File

@@ -129,6 +129,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\m
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}"
ProjectSection(ProjectDependencies) = postProject
{9F94B303-5E21-4364-9362-64426F8DB932} = {9F94B303-5E21-4364-9362-64426F8DB932}
{FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}
{03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871}
{4D971245-7A70-41D5-BAA0-DDB5684CAF51} = {4D971245-7A70-41D5-BAA0-DDB5684CAF51}
@@ -382,6 +383,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GcodePreviewHandler", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-GcodePreviewHandler", "src\modules\previewpane\UnitTests-GcodePreviewHandler\UnitTests-GcodePreviewHandler.csproj", "{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.WebSearch", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.WebSearch\Community.PowerToys.Run.Plugin.WebSearch.csproj", "{9F94B303-5E21-4364-9362-64426F8DB932}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -1027,6 +1030,12 @@ Global
{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|x64.ActiveCfg = Release|x64
{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|x64.Build.0 = Release|x64
{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3}.Release|x86.ActiveCfg = Release|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x64.ActiveCfg = Debug|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x64.Build.0 = Debug|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Debug|x86.ActiveCfg = Debug|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.ActiveCfg = Release|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x64.Build.0 = Release|x64
{9F94B303-5E21-4364-9362-64426F8DB932}.Release|x86.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1151,6 +1160,7 @@ Global
{133281D8-1BCE-4D07-B31E-796612A9609E} = {2F305555-C296-497E-AC20-5FA1B237996A}
{805306FF-A562-4415-8DEF-E493BDC45918} = {2F305555-C296-497E-AC20-5FA1B237996A}
{FCF3E52D-B80A-4FC3-98FD-6391354F0EE3} = {2F305555-C296-497E-AC20-5FA1B237996A}
{9F94B303-5E21-4364-9362-64426F8DB932} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -0,0 +1,11 @@
## Web Search Plugin
The Web Search Plugin, as the name suggests, is used to perform a web search - in the default search engine in the default browser - on the query that has been entered by the user.
![Image of Web Search plugin](/doc/images/launcher/plugins/WebSearch.png)
## Default Browser Icon
- The icon for each web search result is that of the default browser set by the user.
- It, and the browser path, are obtained from the user registry and updated each time the theme of PT Run is changed.
## Score
- The web search result always has a score of 0 which indicates that it would show up after each of the other plugins, other than the indexer plugin and possibly the uri plugin which both have a score of 0.

View File

@@ -14,3 +14,4 @@
- [Sys](/doc/devdocs/modules/launcher/plugins/sys.md)
- [Uri](/doc/devdocs/modules/launcher/plugins/uri.md)
- [Window Walker](/doc/devdocs/modules/launcher/plugins/windowwalker.md)
- [Web Search](/doc/devdocs/modules/launcher/plugins/WebSearch.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -312,6 +312,10 @@
<!-- Plugins -->
<Directory Id="LauncherPluginsFolder" Name="Plugins">
<Directory Id="WebSearchPluginFolder" Name="WebSearch">
<Directory Id="WebSearchImagesFolder" Name="Images" />
<Directory Id="WebSearchLanguagesFolder" Name="Languages" />
</Directory>
<Directory Id="CalculatorPluginFolder" Name="Calculator">
<Directory Id="CalculatorImagesFolder" Name="Images" />
<Directory Id="CalculatorLanguagesFolder" Name="Languages" />
@@ -1008,7 +1012,7 @@
<Fragment>
<!-- Resource directories should be added only if the installer is built on the build farm -->
<?ifdef env.IsPipeline?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder;WindowsSettingsPluginFolder;WindowsTerminalPluginFolder;WebSearchPluginFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<!-- Resource file directories -->
<?foreach Language in $(var.LocLanguageList)?>
@@ -1278,6 +1282,17 @@
<File Id="UnitConverterLight" Source="$(var.BinX64Dir)modules\launcher\Plugins\UnitConverter\Images\unitconverter.light.png" />
<File Id="UnitConverterDark" Source="$(var.BinX64Dir)modules\launcher\Plugins\UnitConverter\Images\unitconverter.dark.png" />
</Component>
<!-- WebSearch Plugin -->
<Component Id="WebSearchComponent" Directory="WebSearchPluginFolder" Guid="C996F2B4-FF2E-4277-9175-494B6A613898" >
<?foreach File in plugin.json;Community.PowerToys.Run.Plugin.WebSearch.deps.json;Community.PowerToys.Run.Plugin.WebSearch.dll?>
<File Id="WebSearch_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\Plugins\WebSearch\$(var.File)" />
<?endforeach?>
</Component>
<Component Id="WebSearchImagesComponent" Directory="WebSearchImagesFolder" Guid="B3A5DA32-FA87-47E6-81F6-0454BA95CCA4">
<File Id="WebSearchLight" Source="$(var.BinX64Dir)modules\launcher\Plugins\WebSearch\Images\WebSearch.light.png" />
<File Id="WebSearchDark" Source="$(var.BinX64Dir)modules\launcher\Plugins\WebSearch\Images\WebSearch.dark.png" />
</Component>
<!-- Uri Plugin -->
<Component Id="UriComponent" Directory="UriPluginFolder" Guid="C7DC8F88-554C-4375-9510-9435399B5D3D">

View File

@@ -0,0 +1,106 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Import Project="..\..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>{9F94B303-5E21-4364-9362-64426F8DB932}</ProjectGuid>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Community.PowerToys.Run.Plugin.WebSearch</RootNamespace>
<AssemblyName>Community.PowerToys.Run.Plugin.WebSearch</AssemblyName>
<Version>$(Version).0</Version>
<useWPF>true</useWPF>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Platforms>x64</Platforms>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\..\..\..\..\x64\Debug\modules\launcher\Plugins\WebSearch\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutputPath>..\..\..\..\..\x64\Release\modules\launcher\Plugins\WebSearch\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
<AdditionalFiles Include="..\..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Wox.Infrastructure\Wox.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Wox.Plugin\Wox.Plugin.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Runtime" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Images\WebSearch.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\WebSearch.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,14 @@
{
"Projects": [
{
"LanguageSet": "Azure_Languages",
"LocItems": [
{
"SourceFile": "src\\modules\\launcher\\Plugins\\Community.PowerToys.Run.Plugin.WebSearch\\Properties\\Resources.resx",
"CopyOption": "LangIDOnName",
"OutputPath": "src\\modules\\launcher\\Plugins\\Community.PowerToys.Run.Plugin.WebSearch\\Properties"
}
]
}
]
}

View File

@@ -0,0 +1,430 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Infrastructure;
using Wox.Plugin;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.WebSearch
{
public class Main : IPlugin, IPluginI18n, IContextMenu, ISettingProvider, IDisposable
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
private static readonly IFile File = FileSystem.File;
private const string NotGlobalIfUri = nameof(NotGlobalIfUri);
/// <summary>If true, dont show global result on queries that are URIs</summary>
private bool _notGlobalIfUri;
private PluginInitContext _context;
private string _searchEngineUrl;
private string _browserName = Properties.Resources.plugin_browser;
private string _browserIconPath;
private string _browserPath;
private string _defaultIconPath;
private bool _disposed;
public string Name => Properties.Resources.plugin_name;
public string Description => Properties.Resources.plugin_description;
public IEnumerable<PluginAdditionalOption> AdditionalOptions => new List<PluginAdditionalOption>()
{
new PluginAdditionalOption()
{
Key = NotGlobalIfUri,
DisplayLabel = Properties.Resources.plugin_global_if_uri,
Value = false,
},
};
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return new List<ContextMenuResult>(0);
}
public List<Result> Query(Query query)
{
if (query is null)
{
throw new ArgumentNullException(nameof(query));
}
var results = new List<Result>();
if (!AreResultsGlobal()
&& query.ActionKeyword == query.RawQuery
&& IsDefaultBrowserSet())
{
string arguments = "\"? \"";
results.Add(new Result
{
Title = Properties.Resources.plugin_description.Remove(Description.Length - 1, 1),
SubTitle = Properties.Resources.plugin_in_browser,
QueryTextDisplay = string.Empty,
IcoPath = _defaultIconPath,
ProgramArguments = arguments,
Action = action =>
{
if (!Helper.OpenInShell(_browserPath, arguments))
{
_context.API.ShowMsg(
$"Plugin: {Properties.Resources.plugin_name}",
$"{Properties.Resources.plugin_search_failed}: ");
return false;
}
return true;
},
});
return results;
}
if (!string.IsNullOrEmpty(query.Search))
{
string searchTerm = query.Search;
// Don't include in global results if the query is a URI (and if the option NotGlobalIfUri is enabled)
if (_notGlobalIfUri
&& AreResultsGlobal()
&& IsURI(searchTerm))
{
return results;
}
var result = new Result
{
Title = searchTerm,
SubTitle = string.Format(System.Globalization.CultureInfo.CurrentCulture, Properties.Resources.plugin_open, _browserName),
QueryTextDisplay = searchTerm,
IcoPath = _defaultIconPath,
};
if (_searchEngineUrl is null)
{
string arguments = $"\"? {searchTerm}\"";
result.ProgramArguments = arguments;
result.Action = action =>
{
if (!Helper.OpenInShell(_browserPath, arguments))
{
_context.API.ShowMsg(
$"Plugin: {Properties.Resources.plugin_name}",
$"{Properties.Resources.plugin_search_failed}: ");
return false;
}
return true;
};
}
else
{
string url = string.Format(System.Globalization.CultureInfo.InvariantCulture, _searchEngineUrl, searchTerm);
result.Action = action =>
{
if (!Helper.OpenInShell(url))
{
_context.API.ShowMsg(
$"Plugin: {Properties.Resources.plugin_name}",
$"{Properties.Resources.plugin_search_failed}: ");
return false;
}
return true;
};
}
results.Add(result);
}
return results;
bool AreResultsGlobal()
{
return string.IsNullOrEmpty(query.ActionKeyword);
}
// Checks if input is a URI the same way Microsoft.Plugin.Uri.UriHelper.ExtendedUriParser does
bool IsURI(string input)
{
if (input.EndsWith(":", StringComparison.OrdinalIgnoreCase)
&& !input.StartsWith("http", StringComparison.OrdinalIgnoreCase)
&& !input.Contains("/", StringComparison.OrdinalIgnoreCase)
&& !input.All(char.IsDigit))
{
return true;
}
if (input.EndsWith(":", StringComparison.CurrentCulture)
|| input.EndsWith(".", StringComparison.CurrentCulture)
|| input.EndsWith(":/", StringComparison.CurrentCulture)
|| input.EndsWith("://", StringComparison.CurrentCulture)
|| input.All(char.IsDigit))
{
return false;
}
try
{
_ = new UriBuilder(input);
}
catch (UriFormatException)
{
return false;
}
return true;
}
}
private bool IsDefaultBrowserSet()
{
return !string.IsNullOrEmpty(_browserPath);
}
public void Init(PluginInitContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
UpdateBrowserIconPath(_context.API.GetCurrentTheme());
}
public string GetTranslatedPluginTitle()
{
return Properties.Resources.plugin_name;
}
public string GetTranslatedPluginDescription()
{
return Properties.Resources.plugin_description;
}
private void OnThemeChanged(Theme oldtheme, Theme newTheme)
{
UpdateIconPath(newTheme);
UpdateBrowserIconPath(newTheme);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Design",
"CA1031:Do not catch general exception types",
Justification = "We want to keep the process alive but will log the exception")]
private void UpdateBrowserIconPath(Theme newTheme)
{
try
{
string progId = GetRegistryValue(
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
"ProgId");
// The `?` argument doesn't work on opera, so we get the user's default search engine:
if (progId.StartsWith("Opera", StringComparison.OrdinalIgnoreCase))
{
// Opera user preferences file:
string prefFile;
if (progId.Contains("GX", StringComparison.OrdinalIgnoreCase))
{
_browserName = "Opera GX";
prefFile = System.IO.File.ReadAllText($"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\Opera Software\\Opera GX Stable\\Preferences");
}
else
{
_browserName = "Opera";
prefFile = System.IO.File.ReadAllText($"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}\\Opera Software\\Opera Stable\\Preferences");
}
// "default_search_provider_data" doesn't exist if the user hasn't searched for the first time,
// therefore we set `url` to opera's default search engine:
string url = "https://www.google.com/search?client=opera&q={0}&sourceid=opera";
using (System.Text.Json.JsonDocument doc = System.Text.Json.JsonDocument.Parse(prefFile))
{
if (doc.RootElement.TryGetProperty("default_search_provider_data", out var element))
{
if (element.TryGetProperty("template_url_data", out element))
{
if (element.TryGetProperty("url", out element))
{
url = element.GetString();
}
}
}
}
url = url
.Replace("{searchTerms}", "{0}", StringComparison.Ordinal)
.Replace("{inputEncoding}", "UTF-8", StringComparison.Ordinal)
.Replace("{outputEncoding}", "UTF-8", StringComparison.Ordinal);
int startIndex = url.IndexOf('}', StringComparison.Ordinal) + 1;
// In case there are other url parameters (e.g. `&foo={bar}`), remove them:
for (int i = url.IndexOf("}", startIndex, StringComparison.Ordinal);
i != -1;
i = url.IndexOf("}", startIndex, StringComparison.Ordinal))
{
for (int j = i - 1; j > 0; --j)
{
if (url[j] == '&')
{
url = url.Remove(j, i - j + 1);
break;
}
}
}
_searchEngineUrl = url;
}
else
{
string appName = GetRegistryValue($"HKEY_CLASSES_ROOT\\{progId}\\Application", "ApplicationName")
?? GetRegistryValue($"HKEY_CLASSES_ROOT\\{progId}", "FriendlyTypeName");
if (appName is null)
{
appName = Properties.Resources.plugin_browser;
}
else
{
// Handle indirect strings:
if (appName.StartsWith("@", StringComparison.Ordinal))
{
appName = GetIndirectString(appName);
}
appName = appName
.Replace("URL", null, StringComparison.OrdinalIgnoreCase)
.Replace("HTML", null, StringComparison.OrdinalIgnoreCase)
.Replace("Document", null, StringComparison.OrdinalIgnoreCase)
.TrimEnd();
}
_browserName = appName;
_searchEngineUrl = null;
}
var programLocation =
// Resolve App Icon (UWP)
GetRegistryValue(
"HKEY_CLASSES_ROOT\\" + progId + "\\Application",
"ApplicationIcon")
// Resolves default file association icon (UWP + Normal)
?? GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\DefaultIcon", null);
// "Handles 'Indirect Strings' (UWP programs)"
// Using Ordinal since this is internal and used with a symbol
if (programLocation.StartsWith("@", StringComparison.Ordinal))
{
// Check if there's a postfix with contract-white/contrast-black icon is available and use that instead
string directProgramLocation = GetIndirectString(programLocation);
var themeIcon = newTheme == Theme.Light || newTheme == Theme.HighContrastWhite
? "contrast-white"
: "contrast-black";
var extension = Path.GetExtension(directProgramLocation);
var themedProgLocation =
$"{directProgramLocation.Substring(0, directProgramLocation.Length - extension.Length)}_{themeIcon}{extension}";
_browserIconPath = File.Exists(themedProgLocation)
? themedProgLocation
: directProgramLocation;
}
else
{
// Using Ordinal since this is internal and used with a symbol
var indexOfComma = programLocation.IndexOf(',', StringComparison.Ordinal);
_browserIconPath = indexOfComma > 0
? programLocation.Substring(0, indexOfComma)
: programLocation;
_browserPath = _browserIconPath;
}
}
catch (Exception e)
{
_browserIconPath = _defaultIconPath;
Log.Exception("Exception when retrieving icon", e, GetType());
}
string GetRegistryValue(string registryLocation, string valueName)
{
return Microsoft.Win32.Registry.GetValue(registryLocation, valueName, null) as string;
}
string GetIndirectString(string str)
{
var stringBuilder = new StringBuilder(128);
if (NativeMethods.SHLoadIndirectString(
str,
stringBuilder,
(uint)stringBuilder.Capacity,
IntPtr.Zero)
== NativeMethods.Hresult.Ok)
{
return stringBuilder.ToString();
}
throw new Exception("Could not load indirect string.");
}
}
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
_defaultIconPath = "Images/WebSearch.light.png";
}
else
{
_defaultIconPath = "Images/WebSearch.dark.png";
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
if (_context != null && _context.API != null)
{
_context.API.ThemeChanged -= OnThemeChanged;
}
_disposed = true;
}
}
public Control CreateSettingPanel()
{
throw new NotImplementedException();
}
public void UpdateSettings(PowerLauncherPluginSettings settings)
{
_notGlobalIfUri = settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == NotGlobalIfUri)?.Value ?? false;
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Community.PowerToys.Run.Plugin.WebSearch
{
internal static class NativeMethods
{
internal enum Hresult : uint
{
Ok = 0x0000,
}
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
}
}

View File

@@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Community.PowerToys.Run.Plugin.WebSearch.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Community.PowerToys.Run.Plugin.WebSearch.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to the default browser.
/// </summary>
public static string plugin_browser {
get {
return ResourceManager.GetString("plugin_browser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search the web..
/// </summary>
public static string plugin_description {
get {
return ResourceManager.GetString("plugin_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Don&apos;t include in global results on queries that are URIs.
/// </summary>
public static string plugin_global_if_uri {
get {
return ResourceManager.GetString("plugin_global_if_uri", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to In the default browser.
/// </summary>
public static string plugin_in_browser {
get {
return ResourceManager.GetString("plugin_in_browser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Web Search.
/// </summary>
public static string plugin_name {
get {
return ResourceManager.GetString("plugin_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search the web in {0}.
/// </summary>
public static string plugin_open {
get {
return ResourceManager.GetString("plugin_open", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to open the default browser.
/// </summary>
public static string plugin_search_failed {
get {
return ResourceManager.GetString("plugin_search_failed", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="plugin_browser" xml:space="preserve">
<value>the default browser</value>
</data>
<data name="plugin_description" xml:space="preserve">
<value>Search the web.</value>
</data>
<data name="plugin_global_if_uri" xml:space="preserve">
<value>Don't include in global results on queries that are URIs</value>
</data>
<data name="plugin_in_browser" xml:space="preserve">
<value>In the default browser</value>
</data>
<data name="plugin_name" xml:space="preserve">
<value>Web Search</value>
</data>
<data name="plugin_open" xml:space="preserve">
<value>Search the web in {0}</value>
</data>
<data name="plugin_search_failed" xml:space="preserve">
<value>Failed to open the default browser</value>
</data>
</root>

View File

@@ -0,0 +1,13 @@
{
"ID": "9F1B49201C3F4BF781CAAD5CD88EA4DC",
"ActionKeyword": "??",
"IsGlobal": true,
"Name": "Web Search",
"Author": "cyberrex5",
"Version": "1.0.0",
"Language": "csharp",
"Website": "https://aka.ms/powertoys",
"ExecuteFileName": "Community.PowerToys.Run.Plugin.WebSearch.dll",
"IcoPathDark": "Images\\WebSearch.dark.png",
"IcoPathLight": "Images\\WebSearch.light.png"
}