LoginSignup
25
22

More than 5 years have passed since last update.

Goでブラウザの右クリックからコマンドを実行してみよう

Last updated at Posted at 2015-04-01

毎日使うブラウザから、気軽にコマンドを呼び出せたら素敵でない?そんなことを思い、kamakura.go #2でGoを使った方法を考えてみました。TL;DRという方は、発表スライドをご覧ください。

ポイント

今回のポイントは、Chromeとネイティブアプリで通信することです。幸い、ChromeはNative Messaging Host APIという、拡張機能とネイティブアプリで通信する機能を持っています。

利用するために、以下の3つの作業を行いました。しかし、Goと関係ないのでここでは取り上げません。

  1. Native Messaging Hostの登録
  2. 拡張機能の登録
  3. Native Messaging Hostの配置

詳しくは、Googleのドキュメントを参照してください。

Chrome Native Messaging

拡張機能とネイティブアプリの通信方法

通信は標準入力と標準出力を介して行われます。メッセージはJSONでシリアライズされており、その先頭に32ビットのメッセージ長が付加されます。

今回は、このプロトコルに従って通信するための小さなライブラリをGoで書きました。Native Messaging APIを介して、Chrome拡張機能と通信できます。

lhside/chrome-go

ライブラリのデモとして、ブラウザからRubyインタプリタを実行してみました。デモの流れは、次のとおりです。

  1. Rubyスクリプトのテキストを選択する
  2. 右クリックからRubyインタプリタを実行する
  3. 実行結果をアラートダイアログが表示される

Goのソースコードは、以下のように短いものです。

package chrome

import (
  "encoding/binary"
  "io"
)

var byteOrder binary.ByteOrder = binary.LittleEndian

func Post(msg []byte, writer io.Writer) error {
  // Post message length in native byte order
  header := make([]byte, 4)
  byteOrder.PutUint32(header, (uint32)(len(msg)))
  if n, err := writer.Write(header); err != nil || n != len(header) {
    return err
  }

  // Post message body
  if n, err := writer.Write(msg); err != nil || n != len(msg) {
    return err
  }
  return nil
}

func Receive(reader io.Reader) ([]byte, error) {
  // Read message length in native byte order
  var length uint32
  if err := binary.Read(reader, byteOrder, &length); err != nil {
    return nil, err
  }

  // Return if no message
  if length == 0 {
    return nil, nil
  }

  // Read message body
  received := make([]byte, length)
  if n, err := reader.Read(received); err != nil || n != len(received) {
    return nil, err
  }
  return received, nil
}

まとめ

毎日利用するブラウザから、ターミナルへテキストをコピー&ペーストすることは手間です。そんな手間を最小にするための方法の一つを、Goで知ることができました。

Goには、バイナリデータを扱う標準パッケージが用意されています。bytesやencoding/binaryを学ぶ、よい題材でした。

最後に、kamakura.goは、Goについて学ぶ良い機会でした。鎌倉 小町通で、焼き鳥エンジニアから色々勉強させて頂きました。。。

サンプルコード

package main

import (
  "os"
  "os/exec"
  "log"
  "encoding/json"

  "github.com/lhside/chrome-go"
)

type Message struct {
  Text, Result string
}

func main() {
  // Receive message
  msg, err := chrome.Receive(os.Stdin)
  if err != nil {
    log.Fatal(err)
  }
  message := &Message{}
  if err := json.Unmarshal(msg, message); err != nil {
    log.Fatal(err)
  }
  log.Println(message.Text)

  // Execute message text as ruby script
  cmd:=exec.Command("ruby", "-e", message.Text)
  result, err := cmd.CombinedOutput()
  if err != nil {
    log.Fatal(err)
  }
  log.Printf("%s\n", result)

  // Post message with the result
  message.Result = string(result)
  body, err := json.Marshal(message)
  if err != nil {
    log.Fatal(err)
  }
  err = chrome.Post(body, os.Stdout)
  if err != nil {
    log.Fatal(err)
  }
}
25
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
22