Microsoftが出しているPowerToysというユーティリティ集が存在する。便利なユーティリティが集合したものだが、詳細についてはこちらを見て欲しい。
今回はこの中のPowerToys Runに注目する。PowerToys Runとは、ショートカットキーによって検索窓を表示してくれる機能で、macOSのSpotlightのWindows版と想像してくれるとわかりやすいと思う。
この検索結果一覧は様々な種類があり、例えば ファイル/フォルダの検索や、そのまま標準ブラウザを開いてくれる機能、VSCodeのワークスペースを検索する機能などがある。これらは、内部ではPluginとして個別に実装されている。
PowerToys/src/modules/launcher/Plugins at main · microsoft/PowerToys · GitHub
これが、以下のパスに設置されている。
C:\Program Files\PowerToys\modules\launcher\Plugins
これに対し、自分で好きな実装を施したプラグインを作るのが今回の目的。
その前に
現時点でサードパーティ製のプラグインはunsupportedなもので、扱いについては現在もこちらのissueで議論され続けているので、もしかしたら急にすべてが壊れるかもしれない。しかし、好意的な方向には見えるのでそんなに大きく事が変わることはないはず。
かんたんなプラグインを作る
まず、Pluginsの依存する各種ライブラリを用意するところから始める。以下の4つが対象だが、2022年5月29日現在はNuGet等には公開されていない*1ので PowerToys を自分でビルドするか、PowerToysインストール後に C:\Program Files\PowerToys\modules\launcher
に設置されるそれぞれをコピーしておく必要がある。
PowerToys.Common.UI.dll
PowerToys.ManagedCommon.dll
Wox.Infrastructure.dll
Wox.Plugin.dll
次にプロジェクトを作成する。あまり.Netの文化に詳しくないが、net6.0-windows
か netcoreapp3.1
を対象にしたプロジェクトを用意する必要がある。どうするのが正攻法なのかはわからないが、クラスライブラリ
をもとにプロジェクトを作成し、直接 .csproj
を触っている。そして、依存についてこんな感じになるように追記していく。パスについては適時お好みの配置に換えてください。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0-windows</TargetFramework> <useWPF>true</useWPF> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <Reference Include="PowerToys.Common.UI"> <HintPath>..\libs\PowerToys.Common.UI.dll</HintPath> </Reference> <Reference Include="PowerToys.ManagedCommon"> <HintPath>..\libs\PowerToys.ManagedCommon.dll</HintPath> </Reference> <Reference Include="Wox.Infrastructure"> <HintPath>..\libs\Wox.Infrastructure.dll</HintPath> </Reference> <Reference Include="Wox.Plugin"> <HintPath>..\libs\Wox.Plugin.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> <None Update="images\icon.png"> <!-- あとで作成する --> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> <None Update="plugin.json"> <!-- あとで作成する --> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> </Project>
Main.cs
を次のようにする。最小に近い状態。
using System.Windows; using ManagedCommon; using Wox.Plugin; namespace PowerToysRunPluginSample { public class Main : IPlugin { private string? IconPath { get; set; } private PluginInitContext? Context { get; set; } public string Name => "Cool Sample"; public string Description => "This is cool sample plugin"; public List<Result> Query(Query query) { return new List<Result> { new Result { Title = "Copy COOL", SubTitle = "Copy COOL", IcoPath = IconPath, Action = e => { Clipboard.SetText("COOL"); return true; }, }, new Result { Title = $"Copy {query.Search}", SubTitle = $"Copy {query.Search}", IcoPath = IconPath, Action = e => { Clipboard.SetText(query.Search); return true; }, }, }; } public void Init(PluginInitContext context) { Context = context; Context.API.ThemeChanged += OnThemeChanged; UpdateIconPath(Context.API.GetCurrentTheme()); } private void UpdateIconPath(Theme theme) { IconPath = "images/icon.png"; } private void OnThemeChanged(Theme currentTheme, Theme newTheme) { UpdateIconPath(newTheme); } } }
images\icon.png
を用意する。どういったものが要求されるかは真面目に見ていない。
plugin.json
を用意する。次のような感じ。気になりどころを抑えておく。ID
はGUID?ActionKeyword
はクエリのprefixとして必要なものになるっぽい。sample
であれば、sample hogefuga
といった形でクエリを入れることになる。isGlobal
は更に踏み込んで、このprefixすら必要ないものになるっぽい。その場合、スコアという概念が計算され、高いものであればあるほど高い順位に設定される様子。
{ "ID": "EF1F634F20484459A3679B4FE7B07998", "Disabled": false, "ActionKeyword": "sample", "Name": "PowerToysRunPluginSample", "Author": "naari3", "Version": "1.0.0", "Language": "csharp", "Website": "https://github.com/naari3/PowerToysRunPluginSample", "ExecuteFileName": "PowerToysRunPluginSample.dll", "IsGlobal": false, "IcoPathDark": "images\\icon.png", "IcoPathLight": "images\\icon.png" }
この状態でビルドする。bin/Debug
あたりに色々吐き出されるが、このうち以下の4つを C:\Program Files\PowerToys\modules\launcher\Plugins\PowerToysRunPluginSample
フォルダにコピーする。
images
plugin.json
PowerToysRunPluginSample.deps.json
PowerToysRunPluginSample.dll
この状態で PowerToys を再起動し、ショートカットキーから PowerToys Run を起動すると以下のようにプラグインの挙動を実現できる。
もし何らかうまく行っていない場合、ログを見ると良い。以下の場所にバージョンごとのログがあり、何かに失敗した場合はおそらくここにその情報が追記されている。
%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Logs
ここまでの状態をGitHubのリポジトリとして上げているので、そちらで見たい人はどうぞ
NuGetから依存関係を追加する
NuGetから依存関係を追加し、何らかの挙動を追加したくなるのだが、これが結構大変だった。結論から書くと、結局依存関係を解決してくれなかったためILRepackなどでひとつのdllにマージする必要があった。C#のdllの扱いを全く知らないので憶測でしかないし、おま環かもしれないが、おそらく公式プラグインに必要な依存はなにか別の方法で解決されているのだと思う。それっぽいものは C:\Program Files\PowerToys\modules\launcher
に置いているようだったので、このあたりには何か関係していそうな気はする。
ので、依存関係をすべてマージする方法を書く。ILRepackというのは過去存在したILMergeというツールのフォーク版らしい*2。
まず PropertyGroup
に <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
を追加する。これにより、依存したライブラリもOutDirに出力されるようになる。
<PropertyGroup> <!-- snip --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- snip --> </PropertyGroup>
その後、ILRepack.Lib.MSBuild.Task
をNuGet経由で追加し、ILRepack.targets
に以下のように記述する。これにより、dllが結合されるようになる。この際、依存したライブラリが依存しているライブラリも追加する必要があるので注意が必要。
<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="ILRepacker" AfterTargets="Build"> <ItemGroup> <InputAssemblies Include="$(OutputPath)\依存したライブラリ.dll" /> <InputAssemblies Include="$(OutputPath)\依存したライブラリ2.dll" /> <InputAssemblies Include="$(OutputPath)\$(AssemblyName).dll" /> </ItemGroup> <ILRepack Parallel="true" Internalize="true" InternalizeExclude="@(DoNotInternalizeAssemblies)" InputAssemblies="@(InputAssemblies)" TargetKind="Dll" OutputFile="$(OutputPath)\$(AssemblyName).dll" /> </Target> </Project>
この状態でビルドし、dllを追加すると動くようになる。お試しあれ。
参考実装たち
- GitHub - naari3/terraria-japan-wiki-PowerToysRun: PowerToys Run Plugin for Terraria Japan Wiki Search
- 何を隠そう、Terrariaのために今回の事を調べたのだ
- Terrariaについての記事はこちら
- GitHub - naari3/PowerToysRunPluginSample: Plugin sample of PowerToys Run
- 用意したサンプル
- GitHub - skttl/ptrun-guid: Guid Generator for Microsoft PowerToys Run
- ランダムなGUIDを一発で出すためのプラグイン
- GEmojiSharp/src/GEmojiSharp.PowerToysRun at master · hlaueriksson/GEmojiSharp · GitHub
emoji tada
で 🎉 が出てきてマジで便利
- GitHub - lin-ycv/EverythingPowerToys: Everything search plugin for PowerToys Run
- Everythingの検索のガワになる PowerToys Runでも超高速検索が可能に!