Calendar

January 2010
M T W T F S S
    Feb »
 123
45678910
11121314151617
18192021222324
25262728293031

WebkitとPythonを使ったアプリケーション開発


はじめに

組み込みアプリケーション開発、特にセットトップボックス(STB)やデジタル家電、携帯電話などそれなりのUIを要求される製品の開発では、Webブラウザを搭載しユーザーインターフェース(UI)をHTML+JavaScriptで構築することがあります。この手法のメリットとして以下のようなことが挙げられます。

  1. UIとロジックの分離

    家電製品に搭載するような画面の動きをプログラマが全てコードで記述しようとすると大変です。UIをHTML(+CSS)で記述できれば画面作成はデザイナーにお願いし、プログラマーはロジックに集中できます。最後のUIとロジックの結合作業はプログラマー側に残りますが…

  2. 開発工数の削減

    UIをHTMLで記述できれば、C/C++やJavaで記述するのに比べて開発効率の向上が見込まれます。それに伴い、工数削減、開発期間短縮が期待出来ます。

  3. 豊かな表現力、操作性の実現

    ブラウザがFlashなどのプラグインを使用できれば高度な視覚効果を比較的簡単に実現出来ます。

本記事では、これらのメリットをPC上のアプリケーションにも適用することを目指し、サンプルアプリケーションの作成を行います。

システム構成

UIにWebブラウザを利用する場合の典型的なシステム構成は次のようになります。この場合、UIと処理エンジンはブラウザを介して双方向に通信し、連携して動作します。

Diagram1

図1:組み込みブラウザを使った一般的な構成図

“組み込み ブラウザ” をキーワードにネットで検索すれば、多くの組込み用ブラウザ製品がヒットし、この図と同じような資料や説明が見つかると思います。

今回はブラウザ機能にWebKit、アプリケーションロジックにPythonを使い、上の図と似たような構成のアプリケーションを作っていきます。

WebKitとは

オープンソースのHTMLレンダリングエンジンの一つで、Appleの「Safari」や、Googleの「Google Chrome」などのWebブラウザに使われています。
Webブラウザ以外では、Appleの「iTunes」、Googleの「Android」などに使われています。

Pythonとは

プログラミング言語の一つです。Perl、PHP、Rubyと比較されるスクリプト言語です。

なぜPythonなのか

Pythonネタを探してて、このネタに辿り着いたというのが本音ですが….それはおいといて、

Androidであれば、WebKit + Java の開発環境をGoogleが用意してくれています。また、Mac OS 上ならWebKit + Objective-C が使えます。これらの組み合せであればネット上で豊富な資料が入手可能ですが、それなりの開発環境構築の手間がかかります。

WebKit自体は、各種バインディングを利用すれば、特定の言語に縛られず様々な言語から利用出来ます。今回は言語の一例としてPythonを取り上げ、お手軽なアプリケーション開発を目指します。

準備するもの

今回のアプリケーション開発・実行には、次のソフトウェアが必要になります。(必要な順に挙げています)

OS – Linux

記事の作成にはUbuntu9.10と、9.04を使用しました。

Ubuntu以外のディストビリューションでも動作可能と思います。(勿論、BSD系でも)

WebKit

WebKit公式ページ

私の環境では、Google Chrome (Linux版) をインストールした時に、同時にインストールされました。
Ubuntuの場合、後述するpython-webkitをインストールすると一緒に入るかも?(未確認です)

Python

Python公式ページ

UbuntuでX Window Systemを使っている場合、デフォルトでインストールされます。
他のディストリビューションでもデフォルトでインストールされているか、簡単にインストール出来ると思います。

python-webkit

http://code.google.com/p/pywebkitgtk/

正式名称は、WebKit/Gtk Python bindings で、Python+GTK の環境からWebKitを利用できるようにします。

Ubuntuの場合、apt-getでインストール出来ます。

$ sudo apt-get install python-webkit

python-jswebkit

PythonとJavaScriptエンジンの連携を実現します。

公式HPは、上述のpython-webkitと同じようですが、何故かこちら(gwrite)にソースコードが置いてあります。

http://code.google.com/p/gwrite/downloads/list

Ubuntu9.10の場合、上のURIに登録されているパッケージを使えば、簡単にインストール出来ます。

それ以外の方は、

http://gwrite.googlecode.com/files/python-jswebkit-0.0.1.tar.gz

をダウンロードして解凍します。

解凍後、インストールスクリプトを実行します。

$ sudo python setup.py install

インストールの際、libwebkit-dev が必要です。/usr/include/webkit-1.0/以下のファイルを要求された場合は、libwebkit-devをインストールして下さい。

WebKitとPythonでどんなことが出来るか?

python-webkitについてくるサンプルアプリケーションを実行してみましょう。

Ubuntu9.10 (python-webkit 1.1.5-1) の場合、

$ /usr/share/doc/python-webkit/examples/tabbed_browser.py

Ubuntu9.04 (python-webkit 1.0.2-1) の場合、

$ /usr/share/doc/python-webkit/examples/webbrowser.py

問題がなければ、次のようなアプリが起動するはずです。

Screenshot-PyWebKitGtk

図2:python-webkit付属のサンプル・アプリケーション

…立派なWebブラウザが実行されましたね。

ということで、Webブラウザは既にあるので、今回はWebブラウザ以外を目指すことにします。

クリアすべき課題

単にダイアログにWebKitをはめ込んでHTMLのページを表示だけでは面白くありません。(単なるブラウザです)

UI(HTML+JavaScript)とアプリケーションロジック(Python)が連携してアプリケーションらしい動きを目指します。

そのためには、

  1. PythonからJavaScriptの呼び出し
  2. JavaSctiptからPythonの呼び出し

を行う必要があります。

その上で、JavaScriptだけでは実現できないような機能をPython側で行います。

PythonからJavaScriptの呼び出し

python-jswebkit を利用します。

JavaScriptCoreのインスタンスを取得し、EvaluateScript()メソッドを使って呼び出します。以下にサンプルを示します。

記述例
  1. インポート
    import webkit
    import jswebkit
  2. ‘load-finished’シグナルのハンドラで、JavaScriptCoreを取得
    def _cb_load_finished(self, view, frame):
        gc = frame.get_global_context()
        self._javascript = jswebkit.JSContext(gc)
  3. JavaScriptコードを実行
    result = self._javascript.EvaluateScript('JavaScriptのコード')
JavaScriptの記述方法

上の例の ‘JavaScriptのコード’ の部分には、任意のJavaScriptのコードを記述できます。

  1. 関数名()を書けば、JavaScriptの関数を実行出来ます。
    ret = self._javascript.EvaluateScript('foo()')

    と実行すると、JavaScriptのfoo()関数が実行されます。

  2. グローバル変数名を書けば、変数の値を取得出来ます。

    JavaScriptで

    var hoge="hogeの中身";     // これはグローバル変数

    と書いおいて、Pythonから

    result = self._javascript.EvaluateScript('hoge')

    と実行すると、result変数に Javasctript変数「hoge」の値 「hogeの中身」 が格納されます。

補足

もっと簡単にJavaScriptを呼び出す方法として、python-webkit のwebkit.WebView.execute_script()メソッドを使うという方法もありますが、この方法ではJavaScriptの実行結果を取得出来ないため、今回は採用していません。

JavaSctiptからPythonの呼び出し

Android(WebKit+Java)であれば、addJavaScriptInterface()でJava側のインターフェースを登録しておけばJavaScriptから簡単に呼び出せるのですが…
python-webkit, python-jswebkitの両方でいろいろ試しましたが、どちらを使っても JavaScript -> Pythonの呼び出しがうまく出来ませんでした。

そのため今回は、JavaSctiptからPythonの呼び出しはあきらめ、Python側からJavaScriptの関数を定期的に呼び出してメッセージを取得することにします。

アプリケーションの仕様

今回作成するアプリケーションの仕様を定義します。

基本仕様

選択された画像ファイルを表示するアプリケーション、ということにします。基本機能は次の通りです。

  1. ユーザーが指定したフォルダにあるファイルの一覧を表示する。
  2. ユーザーが選択したファイルの画像を表示する。
この仕様に決めた理由

次のような理由から、アプリケーションの仕様を決めました。

  1. JavaScriptのみで実現不可能

    JavaScriptからローカルリソースにアクセス出来ないため、JavaScriptのみでファイル一覧を表示することは出来ません。この部分をPythonと連携して実現することで単なるブラウザ以上のことを実現します。

  2. UIにブラウザを使うメリットを享受出来る。

    さまざまな画像ファイルのフォーマットを知らなくても、ブラウザさえ対応していれば簡単に画像を表示できます。「ブラウザの機能が使えて良かった」と実感出来ると思います。

画面イメージ

画面イメージは下の通りです。

Screenshot-image_viewer

図3:実行画面
制限事項

本記事のコンセプトに合わせて、アプリケーション作成に以下の制限を設けました。

  1. UIは全てHTML+JavaScript(+CSS) で作成する。

    Pythonにはダイアログ表示以外のUIの機能を持たせません。
    ダイアログも全領域にWebKitを表示するだけです。

  2. 表示ファイル(html)はローカルに持ち、ファイル名固定で良い。

    ブラウザではないので、手元にあるファイルの表示で十分です。

アプリケーションの使い方

今回作成したサンプルアプリケーション一式はここにあります。
サンプルアプリケーション一式

ファイルをダウンロードして解凍すると次のファイルが出てきます。

menu.html
image_viewer.py
no_image.png

以下のように入力してアプリケーションを起動します。

$ python image_viewer.py

成功すれば、先に挙げた実行画面が現れると思います。

使い方は次の通りです。

  1. Directory: に画像ファイルのあるパスを指定。

    実行画面例では、GIMPを画像フォルダ /usr/share/gimp/2.0/imagesを指定しています。

  2. [ファイル一覧更新]ボタンを押す。
  3. リストボックスにファイル一覧が表示されるので、画像ファイルを選択。

    画像表示領域に、選択した画像ファイルを表示します。
    画像以外のファイルを選択すると、画像表示領域に「NO IMAGE」と表示します。

  4. 1 に戻って、別のパスを指定することも可能。

アプリケーションの解説

作成したアプリケーションについて説明します。

シナリオ

アプリケーションの利用シナリオを定義します。

登場するアクター、機能は次の通りです。

  1. ユーザー
  2. UI (HTML+JavaScript)
  3. Python

今回は、次の3つの操作シナリオを想定しました。

ファイル一覧表示
  1. ユーザーが、パスを入力し[ファイル一覧更新]ボタンを押して確定する。
  2. UIは、パス名をJavaScriptのグローバル変数に格納する。
  3. Pythonは、
    1. 定期的にJavaScript関数を呼び出し、パス名を監視する。
    2. パス名を取得できたら、指定パスにあるファイル名のリストを作成する。

      JavaScriptからファイル一覧を作成することは出来ません。そのためファイル一覧の作成をPython側で行います。

    3. JavaScript関数を呼び出し、ファイルリストを渡す。
  4. UIは、リストボックスにファイルリストを登録する。(リストの内容は全更新)
画像表示
  1. ユーザーがリストボックスからファイルを一つ選択する。
  2. UIは、
    1. ファイル選択イベントを監視し、
    2. 選択されたファイルをimageエリアに画像を表示する。

      選択ファイルが画像以外の場合、あらかじめ決められた画像(“NO IMAGE”)を表示

終了
  1. ユーザーが、 [終了]ボタンを押す。
  2. UIは、JavaScriptのグローバル変数に終了リクエストを格納する。
  3. Pythonは、
    1. 定期的にJavaScript関数を呼び出し、終了リクエストを監視する
    2. 終了リクエスト:ONなら、プログラムを終了する

ファイル構成

本アプリケーションは、次の3つのファイルから構成されます。

  1. menu.html

    UIを実装したHTML(+JavaScript)ファイルです。

  2. image_viewer.py

    WebKitの表示を行うPythonスクリプトです。

  3. no_image.png

    画像以外のファイルを選択されたときに表示する画像ファイルです。

設計資料

内部構成

アプリケーションの内部構成(WebKit, UI, Python ) は次の様になっています。
component

図4:内部構成図

呼び出しは、Python -> JavaScript の一方向のみで、次の2つの呼び出しを行っています。

  • イベント監視

    GUIのイベントが発生してもJavaScriptからPythonのコードを呼び出せないため、自発的にイベントを通知することが出来ません。そのため、JavaScript側ではイベントをキューにストアしておき、Python側からタイマーイベントで定期的にJavaScriptのget_event()関数を呼び出してイベントを取り出しています。

  • ファイル一覧通知

    ファイル一覧を更新するときに、JavaScriptのset_file_list()関数を呼び出します。

それでは、実際のソースコードについて説明していきます。

Python側のソースコード (image_viewer.py)

まずは、Python側のソースから見ていきます。

#!/usr/bin/env python
# coding: UTF-8
from gettext import gettext as _
import gtk
import webkit
import gobject
import jswebkit
import time
import sys
import dircache
import os

class WebBrowser(gtk.Window):

    def __init__(self):
        """
        (処理)
        1. WebKitのインスタンスを生成し、ダイアログへ追加
        2. シグナル(’load-finished’)に対するコールバック関数(_cb_load_finished)の登録
        3. ダイアログを表示
        4. UI定義ファイル(menu.html)を表示
        """
        gtk.Window.__init__(self)

        self._cmd = {'update':self._cmd_update, 'quit':self._cmd_quit }

        self._browser = webkit.WebView()
        self._browser.connect('load-finished', self._cb_load_finished)
        self.add(self._browser)

        self.set_size_request(640, 460)
        #self.fullscreen()

        self.connect('destroy', gtk.main_quit)
        self.show_all()
        self._browser.open("file://menu.html")

    def _cb_load_finished(self, view, frame):
        """
        シグナル(’load-finished’)に対するコールバック関数。
        HTMLコンテンツのロードが完了したときに、シグナルが通知(コールバック)される。
        (処理)
        1. JavaScriptCore(JavaScript実行エンジン)の取得(メンバー変数に登録)
        2. 監視タイマーの登録
        """
        print "=> load-finished"
        gc = frame.get_global_context()
        self._javascript = jswebkit.JSContext(gc)
        self._set_timer()

    def _set_timer(self):
        gobject.timeout_add(100, self._cb_timer, None)

    def _cb_timer(self, params) :
        """
        タイマーハンドラ
        (処理)
        1. JavaScriptの get_event()関数を呼び出してメッセージ取得
        2. メッセージ対応したハンドラ関数を実行
        """
        event = self._javascript.EvaluateScript('get_event()')
        if event != None:
            print "event[", event, "]"

            params = event.split()
            if self._cmd.has_key(params[0]):
                self._cmd[params[0]](params[1:])
            else:
                print "### err: event[", params[0], "] unknown ###"

        self._set_timer()

    def _cmd_update(self, params):
        """
        “update”メッセージハンドラ関数
        ファイル一覧を取得し、UIへ通知する。
        """
        resp = 'set_file_list("' + os.path.abspath(params[0]) + os.sep + '", ['
        for file in dircache.listdir(params[0]):
            resp += ('"' + file + '",')
        resp += '])'
        #print resp
        self._javascript.EvaluateScript(resp)

    def _cmd_quit(self, params):
        """
        “quit”メッセージハンドラ関数
        直ちにプログラムを終了する。
        """
        sys.exit()

if __name__ == "__main__":
   gobject.threads_init()
   webbrowser = WebBrowser()
   gtk.main()
補足

JavaScriptCoreの取得は、_cb_load_finished()メソッドで行っています。

gc = frame.get_global_context()
self._javascript = jswebkit.JSContext(gc)

JavaScriptの実行は以下の様に行っています。(_cmd_update()メソッド)

event = self._javascript.EvaluateScript('get_event()')

UI側のソースコード (menu.html)

次にUI側(HTML+JavaScript)のコードです。

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Webkit+Python Sample Application</title>

<style type="text/css">
<!--
div.left_block {float: left; width: 50%;}
div.right_block{float: right; width: 50%;}
-->
</style>

<script type="text/javascript">
<!--
/**
 *  Pyton側へ通知するイベントのキューイングに使用
 */
function Queue(){
	this._queue = new Array();
}

Queue.prototype.push = function(item){
	this._queue.push(item);
}

Queue.prototype.pop = function(){
	return this._queue.length ==0 ? null : this._queue.shift();
}

Queue.prototype.empty = function(){
	return this._queue.length == 0 ? true : false;
} 

var event = new Queue();
var workpath = ".";

/**
 *  [ファイル一覧更新]ボタンが押されたとき、キューにメッセージを格納
 */
function on_update(){
	var target_path = document.form.location.value;
	if(0 < target_path.length){
		event.push("update " + document.form.location.value);
	}
}

/**
 *  [終了]ボタンが押されたとき、キューにメッセージを格納
 */
function on_exit(){
	event.push("quit");
}

/**
 * Python から定期的に呼び出される。
 * キューの先頭にあるメッセージを返す。(キューが空なら null を返す)
 */
function get_event(){
	return event.empty() ? null : event.pop();
}

/**
 * Python からファイル一覧の通知時に呼び出される。
 * ファイル一覧リストボックスの内容を更新する。
 */
function set_file_list(path, list){
	workpath = path;
	var file_list = document.result.file_list;

	while(0 < file_list.length ){
		file_list.removeChild(le_list.options[0]);
	}

	for(var i=0; i<list.length; i++ ){
		file_list.options[file_list.options.length] = new Option(list[i], list[i]);
	}
}

/**
 * ユーザーがファイルを選択した時のイベントハンドラ
 * ファイルを画像領域に表示する。
 */
function on_select(){
	var file_list = document.result.file_list;
	var index = file_list.selectedIndex;
	var uri = "file://" + workpath + file_list.options[index].text;

	document.image1.src = uri;
	//console.log(uri);
}

/**
 * ファイルを画像として表示出来なかったときのイベントハンドラ
 * 代わりにno_image.pngを表示する。
 */
function on_image_error(){
	document.image1.src = "file://no_image.png";
}

//-->
</script>
</head>

<body bgcolor='lavender'>
  <form name="form" >
    Directory: <input type="text" name="location" style="width:80%"/>
  </form>
  <button style="width:100%" onClick='on_update()' >ファイル一覧更新</button>
  <div class="left_block">
    <form name="result">
      <select name="file_list" style="width:90%" size=17 onChange='on_select()'></select>
    </form>
  </div>
  <div class="right_block">
    <a id="image" width="320" height="320" >
      <img name="image1" src="file://no_image.png" alt="" onerror='on_image_error()'/>
    </a>
  </div>

  <button style="width:100%" onClick="on_exit()" >終了</button>
</body>
</html>
補足

Pythonへ渡すメッセージはQueueオブジェクトに格納します。

Python から呼び出される関数は、次の2つです。

  • get_event()
  • set_file_list(path, list)

あとがき

今回は組込みソフトウェアの手法をデスクトップアプリケーションに適用してみました。

WebKitをUI構築ツールの一つと捉えると、非常に便利な開発用ライブラリと考えることが出来ると思います。(GoogleのAndroidもそれを狙っているのでしょうけど)

最初のほうにも述べましたが、WebKitは各種言語向けバインディングを利用することにより、いろいろな言語から利用出来ます。現時点(2010年2月)ではその辺りの情報が少ないかな?と思うので、本記事が参考になれば幸いです。

機会があれば、Pythonに限らず、Perl, Ruby, PHPなど、皆さんの慣れ親しんだ言語との組み合わせをぜひ試してみて下さい。

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>