独自URL Schemeの追加と、そのハンドラの実装

URLには https:// Schemeを利用することが多いが、 mailto: file: のように他にもいろいろなScheme1がある。 また、macOS標準のScheme以外にも独自URL Schemeを追加するアプリケーションも存在する(e.g. org-protocol:)2。 では、これらアプリケーションはどのように登録しているのだろうか?

独自URL Schemeの追加

Figure 1: 独自Schemeを利用したブログの編集ボタン

Figure 1: 独自Schemeを利用したブログの編集ボタン

独自URL Schemeは、Script Editorで作成したApplicationを適宜変更していくと楽に追加できる。 また、URLのハンドリング部分を全てAppleScriptで書いてもいいが、慣れていないので今後のメンテナンスを考えGoで実装したhandlerを呼び出すことにした。

ここでは例として、 go://journal/<date> 形式のリンクを踏むと該当する日記をEmacsで開くものを実装する。 まずはScriptEditorを起動して新規ファイルを作成し、ドキュメント3見つつCode 1のスクリプトを書いた。

on gohandler(input)
  do shell script "/Applications/GoHandler.app/Contents/MacOS/bin/go-scheme-handler " & (quoted form of input)
end gohandler

on open location input
  gohandler(input)
end open location

display dialog "GoHandler handles URL with the `go:` scheme :)"
Code 1: go://.* のURLを受け取るとgo-scheme-handlerを呼ぶスクリプト

これを Application タイプで保存すると、Code 2のような構成のアプリケーションが生成された。 先ほど作成したスクリプトは Gohandler.app/Contents/Resources/Scripts/main.scpt として格納されているので、 再度編集したい場合はこれをopenすればよい。

$ tree GoHandler.app/
GoHandler.app/
└── Contents
    ├── Info.plist
    ├── MacOS
    │   └── applet
    ├── PkgInfo
    └── Resources
        ├── Scripts
        │   └── main.scpt
        ├── applet.icns
        ├── applet.rsrc
        └── description.rtfd
            └── TXT.rtf

5 directories, 7 files
Code 2: Applicationのディレクトリ構成

先程のScriptには go: をバインドする部分がなかったが、この設定は Info.plist の方で定義できる。 定義方法は、Code 3の内容を最上位の <dict> 要素に追加するだけ。

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>Go URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>go</string>
        </array>
    </dict>
</array>
Code 3: アプリケーションをURLSchemeの go にマッピングする設定

TIPS Macアプリの簡単なアイコン変更方法

アイコンにしたい画像をクリップボードにコピーしておいた状態でアプリケーションを右クリック、メニューが表示されたらから Show Info を選択。 ポップアップが表示されたら左上のアイコンをクリックしてペースト。

go-scheme-handlerの実装

URL Schemeとのバインド部分は完成したので、残りのハンドリング部分を実装する。 go-scheme-handler は引数でURLを受け取るので、これをパースしてやりたい処理にハンドリングするだけ。 今後いろいろ追加していきたいので、Code 4のようにホスト部分で切り替えられるようにした。 コードの全体像はLadicle/go-scheme-handlerを参照。

func (h *Handler) handle() error {
        switch h.url.Host {
        // "journal/<name>"
        case "journal":
                return openJournalEditor(h.url.Path)
        default:
                return fmt.Errorf("%q is unknown hostname", h.url.Host)
        }
}
Code 4: 今後いろいろ追加する

最後に、ビルドした実行ファイルを GoHandler.app/Contents/MacOS/bin/ に配置すれば完成。 動作を確認するため、作成したアプリケーションを /Application ディレクトリにコピーし、 Code 5のように go: が正しく登録されたことを確認する。

/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep -B6 "bindings:.*go:"
claim id:           Go URL (0x15ae8)
localizedNames:     "LSDefaultLocalizedValue" = "Go URL"
rank:               Default
bundle:             GoHandler (0x4914)
flags:              apple-internal  url-type (0000000000000042)
roles:              Viewer (0000000000000002)
bindings:           go:
q--
claim id:           Go URL (0x15ae8)
localizedNames:     "LSDefaultLocalizedValue" = "Go URL"
rank:               Default
bundle:             GoHandler (0x4914)
flags:              apple-internal  url-type (0000000000000042)
roles:              Viewer (0000000000000002)
bindings:           go:
Code 5: 登録されていることを確認

独自Scheme利用した編集ボタンをつける

実際にこのハンドラを利用してみる。プライベートなメモ帳でもHugoを利用しているので、 そのページにCode 6のような編集ボタンを設定してみる。Hugoのファイル名は出力元(journal)のファイル名にアンダースコア(_)と日時を付与している。よって、編集するファイル名はこの前半部分を取得すれば良い。

また、Hugoのデフォルトで http: / https: / mailto: 以外のスキーマが禁止されている。 そのため、URLに safeURL 関数をパイプしてこれを回避している4

{{ $editURL := (print "go://journal/" (index (split .File.BaseFileName "_") 0)) }}
<a class="edit-button" href="{{ $editURL | safeURL }}">Edit</a>
Code 6: Hugoのページに編集ボタンを設定する

実際にこの挙動を確認してみる。最初の 1のように該当記事のJournalがEmacs上で開くことが確認できた。

TIPS Chromeの確認ダイアログを初回のみ表示させる

Chromeのv79+から外部アプリケーションを開こうとすると毎回確認ダイアログが表示されるようになった。 これを防ぐためには、Code 7のオプションの有効化が必要。これを有効にすると、「次回以降非表示にする」というチェックボックスがダイアログから選択できるようになる。(設定後にブラウザの再起動必須)

$ defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -bool true
Code 7: 確認ダイアログの表示内容変更
.