ninix の実装と独自拡張について
==============================

SSTP (Sakura Script Transfer Protocol)
--------------------------------------

SEND/1.3 の実装
- - - - - - - -

SEND/1.3 の HWnd: ヘッダは、オリジナルの定義ではウィンドウハンド
ルの番号を表しますが、ninix の実装においては Unix ドメインソケッ
トのアドレス (絶対パス名) を表します。SSTP クライアントはリクエ
ストを送信する前に予め Unix ドメインソケットを作成して接続を受け
付ける状態にしておかなければなりません。例:

    SEND SSTP/1.3
    Sender: client.py
    HWnd: /tmp/.client-unix
    Script: \h\s[0]調子どう？\n\n\q0[#a0][まあまあ]\q1[#a1][イマイチ]\m[wait]
    Entry: #a0,\m[OK]\h\s[0]ふーん。\m[end]\e
    Entry: #a1,\m[NG]\h\s[0]酒に逃げるなヨ！\m[end]\e
    Charset: Shift_JIS

    [EOD]

SSTP サーバは、HWnd: ヘッダで指定された Unix ドメインソケットを
通じてスクリプト中に現れた \m[] タグの引数を SSTP クライアントに
送り返します。上記の例で最初の選択肢が選ばれた場合、SSTP サーバ
は /tmp/.client-unix に接続して以下のデータを送信します。

    +wait\n
    +OK\n
    +end\n
    -\n

データは改行文字 \n (ASCII コード 0x0a) で区切られ、各行は一つの
パケットを表します。パケットの最初の一文字はそのパケットの種類を
表します。

    +    \m[] タグの引数を送るパケット
    -    通信終了を通知するパケット

\m[] タグの引数には Sakura Script のタグ以外の任意の文字列を渡す
ことができます。%selfname などのメタ文字列は展開されて返されます。

EXECUTE/1.5(*注1)
- - - - - -

(*注1) ninix-aya 4.0.3以降削除されました

SSTP に拡張リクエスト EXECUTE/1.5 が追加されています。このリクエ
ストを受け取ると SSTP サーバはデータの再読み込みを開始します。例:

    EXECUTE SSTP/1.5
    Sender: ninix-install
    Command: Reload
    Charset: Shift_JIS

    [EOD]

追加されたコマンドは以下の通りです。

    Reload    データの再読み込みを開始する

このリクエストは SSTP サーバが動いているホストからのみ送ることが
できます。その他のホストからのリクエストに対しては 420 Refuse が
返されます。

SHIORI 拡張イベント(*注2)
-------------------

(*注2) ninix-aya 4.0.3以降削除されました

以下の拡張イベントを発行するようになっています。

OnNinixReloading  データの再読み込みを開始した
OnNinixReloaded   データの再読み込みが終了した

プラグイン(*注3)
----------

(*注3) ninix-aya 4.2以降削除されました
       ninix-aya 4.3以降の新しいプラグインについてはここでは述べません

ninix 用のプラグインを ninix-install コマンドでインストールする
ことにより任意の機能を本体に追加することができます。

ninix-install コマンドでインストール可能なファイルは、プラグイン
本体 (実行可能なプログラムファイル) と plugin.txt ファイルを含む
アーカイブファイル (zip 形式または lha 形式(*注4)) です。

plugin.txt はプラグインの名称や起動方法を記述したテキストファイ
ルです。文字コードは EUC-JP です。(*注5) 例:

    # sstpnews plugin 1.0
    name: SSTP News
    startup: sstpnews.py,--fetch
    menuitem: ニュースを読む,sstpnews.py,--fetch
    menuitem: バージョン情報,sstpnews.py,--version

# で始まる行はコメントです。コメント行と空行は無視されます。

name: ヘッダはプラグインの名前 (識別子) を表します。このヘッダは
必須です。

startup: ヘッダは本体の起動後に一度だけ実行されるコマンドを表し
ます。実行ファイル名と引数をコンマ (,) で区切って指定して下さい。
このヘッダは複数指定することはできません。

menuitem: ヘッダは本体の右クリックメニューに追加する項目を表しま
す。メニューの項目名、実行ファイル名、引数をコンマで区切って指定
して下さい。このヘッダは複数指定することができます。

startup: ヘッダと menuitem: ヘッダはいずれも省略可能です。

プラグインの実行時のカレントディレクトリは実行ファイルの置かれた
ディレクトリになります。プラグインの実行時には以下の環境変数が設
定されます。

    NINIX_PID          本体 (親プロセス) のプロセス ID
    NINIX_SSTP_PORT    利用可能な SSTP ポート番号 (もし無ければ
                       文字列 "none" が設定される)
    NINIX_PLUGIN_DIR   プラグインが格納されているディレクトリ

本体が終了するとプラグインに対して SIGHUP シグナルが送られます。
プラグインが何をしていつ終了するかはプラグイン作成者の自由です。

プラグインと本体との通信には SSTP を用います。同一ホスト上で複数
の本体が動いている可能性がありますので、NINIX_SSTP_PORT で指定さ
れた以外のポート番号は使わないようにして下さい。

(*注4) ninix-aya 4.0.7以降はzip形式のみです

(*注5) ninix-ayaでの仕様拡張:
plugin.txt の中で "charset" を指定することでそれ以降の行では
EUC-JP 以外の文字コードの使用が可能になります。指定をしなかった
場合には EUC-JP であると仮定して処理されます。

Python 栞モジュール(*注6)
-------------------

(*注6) ninix-aya 2.1.5以降削除されました

栞モジュールを shiori.py というファイル名でゴーストのアーカイブ
ファイルに入れることにより、Python で栞モジュールを自作すること
ができます。

shiori.py は以下のモジュール関数を公開しなければなりません。

    list_dict(dir)
    辞書ファイルのリストを返す。このリストで与えられたファイルの
    みがインストールの対象となる。リストの各要素は引数で指定され
    たディレクトリからの絶対パスでなければならない。

    open(dir, debug=0)
    ninix.shiori モジュールの Shiori クラスで定義されたインター
    フェイスを持つクラス (Shiori クラスのサブクラス) のインスタ
    ンスを返す。

簡単な実装例を以下に示します。

----------------------------------------------------------------

import ninix.shiori

import time
import whrandom

def list_dict(dir):
    return []

def open(dir, debug=0):
    return Shiori(dir, debug)

class Shiori(ninix.shiori.Shiori):
    def get_event_response(self, event,
                           ref0=None, ref1=None, ref2=None, ref3=None,
                           ref4=None, ref5=None, ref6=None, ref7=None):
        if event in ["OnFirstBoot", "OnBoot"]:
            return "%username、こんにちは。"
        elif event == "OnMouseDoubleClick":
            return whrandom.choice(["つつかないで下さい。", "何も出ません。"])
        elif event == "OnMinuteChange":
            now = time.localtime(time.time())
            if now[4] == 0:
                return "%d時です。" % now[3]
            elif now[4] == 30:
                return "%d時半です。" % now[3]
        return None
    def getstring(self, name):
        if name == "homeurl":
            return "http://localhost/nanika/"
        return None

----------------------------------------------------------------
