Wox の plugin を作る (Python)

﻿# Wox とは

Windows 上で動作する launcher で, Win + S が凄くなったやつ: http://www.wox.one/

macOS では Alfred (+Powerpack) を愛用しているが, 最近あてがわれた Windows 環境でも似たような使い勝手の launcher と過ごせないかと探していたところ発見した. C# と Python で plugin を作ることができ, Alfred workflow (ほど柔軟ではないにせよ) 同様の拡張を行なえる.

# plugin 開発

前述の通り自分は Alfred 使いなので, Python で書いた自作 Alfred workflow を移植したいため Python を選択した. Python で書かれた plugin を動かすには別途 Python 3 をインストールして Wox に所在を伝える必要があるので, 広く使ってもらうという点で考えると若干ハードルが高いような気がする. 特にしがらみがなければ C# で開発するのが良さそう.

## 原理

公式ドキュメントはこちら:
http://doc.wox.one/en/plugin/create_plugin.html

要は Wox から plugin に user input が渡されてくるので, それに応じた結果を JSON で返すという単純なもの. JSON と言いつつ言語毎に helper class がよしなにしてくれるので開発者は文字列を受けとり dict の配列を返せば良い (Python の場合).
![JSONRPC.png](http://doc.wox.one/en/plugin/images/JSONRPC.png)

plugin が返却した配列を元に Wox が選択肢を表示し, ユーザが選択した項目に対して callback が発生するので処理を行なう (ブラウザやフォルダを開くなり). callback 情報が含まれていない項目は選択できない.
![SoWkIImgAStDuGejJYrIqBLJ2C-lKB1IA2nDBQha0h91GW4vfUcPUN1H21UXjYYrk3Id1AOOP0vGi3WZlrmXEIC_Cpcr6DLig2Y6uNdv9PbfSDCH9YKcbsJcvt4vfEQbWDm90000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924/45e3235a-ee1d-3033-5895-fd9ff727675e.png)

## 開発手順

### Python 3 のインストール

Python plugin を動かすために Wox が使用する.
インストール後に Wox の設定画面からインストール先を指定しておく.

### 作業ディレクトリを決める

- `git init` するとか
- 先にインストールした Python 3 が動くようにしておく (自分は WSL 上に開発環境を作ったので pyenv を使った)

### plugin.json 作成

[公式](http://doc.wox.one/en/plugin/plugin_json.html) を参考に `plugin.json` ファイルを作成する

```
{
  "ID": "1AEB702E890911EABAF20C54152B91EC",  //Plugin ID，32 bit UUID
  "ActionKeyword": "sample",                 //Plugin default action keyword
  "Name": "sample plugin",                   //Plugin name
  "Description": "sample plugin",            //Plugin description
  "Author": "kosugi",                        //Plugin Author
  "Version": "1.0.0",                        //Plugin version，must be x.x.x format
  "Language": "python",                      //Plugin language，we support csharp,python and executable now
  "Website": "https://qiita.com/kosugi",     //Plugin website or author website
  "IcoPath": "Images\\icon.png",             //Plugin icon, relative path to the pluign folder
  "ExecuteFileName": "main.py"               //Execution entry. Dll name for c# plugin, and python file for python plugin
}
```

- ID は `python -c 'import uuid; print(uuid.uuid1().hex.upper())'` などで適当に.
- Version はドキュメントの通り `x.x.x` という書式でないと公式リポジトリへの登録時に弾かれる (手元で動作させる分には問題ない)
- IcoPath は `Images` ディレクトリ配下に設置している例が多いが直下でも問題ない

### wox.py 準備

[公式のサンプルプログラム](http://doc.wox.one/en/plugin/python_plugin.html) を見ると `wox.py` が必要と読めるが, 正しい調達手段が分からない. 恐らく Wox から実行される際にはモジュール検索パスに含まれているのだろうが開発時はどうすれば良いか.

調べた結果, Wox の GitHub リポジトリに含まれていた (リポジトリ自体を clone する必要はなさそう).

```
wget https://raw.githubusercontent.com/Wox-launcher/Wox/master/JsonRPC/wox.py
```

Wox からの実行時に wox.py が存在するとこちらが優先されてしまうためアーカイブ (後述) やインストール先には含めてはならない.

### plugin 本体

```python:main.py
# -*- coding: utf-8 -*-
from wox import Wox

class Main(Wox):

    def query(self, user_input):
        # user_input に対応する結果を返す
        return [{
            'Title': '一行目に表示される (test: ' + user_input + ')',
            'SubTitle': '二行目に表示される',
            'IcoPath': '左に表示されるアイコン.png',
            'JsonRPCAction': {
                'method': 'action', # 選択時に呼ばれるメソッド名
                'parameters': ['method に渡される引数 1', '2...'],
                'dontHideAfterAction': False
            }
        }]

    def action(self, data1, data2):
        # 項目が選択されたので処理する
        pass

if __name__ == '__main__':
    Main()
```

スクリプトを実行すると, コマンドライン引数として与えられた JSON が手元の `wox.py` の `Wox` クラスにより解釈されて `query` メソッドが呼ばれる. 具体的には以下のように実行する.

```
$ python main.py '{"method": "query", "parameters": ["user intput"]}' | jq
{
  "result": [
    {
      "Title": "一行目に表示される (test: user intput)",
      "SubTitle": "二行目に表示される",
      "IcoPath": "左に表示されるアイコン.png",
      "JsonRPCAction": {
        "method": "action",
        "parameters": [
          "method に渡される引数 1",
          "2..."
        ],
        "dontHideAfterAction": false
      }
    }
  ]
}
```

`main.py` を実行する方法だと callback (ここでは `action` メソッドが呼ばれた際の挙動) までは確認できないので実際に Wox にインストールして確認する必要があるが工夫すればテストを書くことも可能だろう.

## Wox へのインストール

- Wox インストール先の `Plugins` ディレクトリ直下に適当な名前でディレクトリを掘り `plugin.json`, `main.py` およびアイコンファイルを置く.
- Wox を再起動するか Wox から `Reload Plugin Data` を実行する.
- Wox から `plugin.json` で指定した ActionKeyword を入力することで呼び出せる

Wox 経由で呼び出すと以下のような表示になる:
![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924/a224c185-4161-3418-2121-472839562bf1.png)

## plugin repository への登録

http://www.wox.one/plugin 上でアカウントを登録すると, 作成した plugin を登録できる. 登録されたものは Wox 上から検索・インストールできるようになる.

登録には, 必要なファイルを ZIP アーカイブしたものを用いる. 拡張子は zip ではなく wox とする.

### 今回作って (登録して) みたもの
![2020-04-28-09-32-50_Trim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924/154fc4ec-4524-f5d8-25ac-f7b71fee95db.gif)

unicode name を検索して該当する文字をクリップボードにコピーしたり, 入力したコードポイントに対応する文字をクリップボードにコピーできる. それぞれ `wpm install Unicodebuilder (query)` および `wpm install Unicodebuilder` でインストールできる. Alfred だと 1 つの workflow に複数の ActionKeyword を含められるが Wox では不可能なので plugin を分けた.

- https://github.com/kosugi/wox.unicodebuilder