みんなでターミナル Partty! 0.1リリース!

1つのターミナルを複数の人で同時に操作できるソフトウェア Partty のバージョン0.1をリリースしました!
万が一Parttyをご存じない方は、こちらをご覧ください。


http://f.hatena.ne.jp/images/fotolife/v/viver/20070721/20070721051919.png
ダウンロード: partty-0.1.tar.gz

コンパイル

LinuxMac OS Xで動作することを確認しています。LinuxではMakefileを以下のように修正してください。

 # for Linux
-#LDFLAGS += -lutil
+LDFLAGS += -lutil


そしてmakeしてください。

$ make

うまくいけばparttyという名前のバイナリができます。うまくいかなかったら、いろいろいじってみてください。


使い方

* Partty Host (create new session)
   [listen on TCP  ]$ partty  host  # use default port [25770]
   [listen on TCP  ]$ partty  host  <port number>
   [listen on TCP  ]$ partty  host  <network interface name>[:<port number>]
   [listen on TCP  ]$ partty  host  <listen address>[:<port number>]
   [listen on TCP  ]$ partty  host  <listen address>[:<port number>]
   [lisetn on Local]$ partty  host  -l  <path to socket to create>

* Partty Guest (join existent session)
   [connect to TCP  ]$ partty  guest  <hostname>  # use default port [25770]
   [connect to TCP  ]$ partty  guest  <hostname>:<port number>
   [connect to Local]$ partty  guest  -l  <path to socket>

まず、Partty Hostを実行してください。続いて別のターミナルまたは別のコンピュータで、Partty Guestを実行してください。すると、Partty Host側のターミナルを2カ所から同時に見たり、操作したりできます。
さらにPartty Guestをいくつも接続して、みんなでターミナルを操作できます。みんなでPartty!

※ 間違っても同じターミナルでGuestを起動してはいけません。大変なことになります。


たとえば「driver.local」「partner.local」「spectator.local」の3台のコンピュータで1つのターミナルを共有するには、以下のようにします。

[driver.local. ~/partty]$ ./partty host
[partner.local. ~/partty]$ ./partty guest driver.local
[spectator.local. ~/partty]$ ./partty guest driver.local

UNIXドメインソケット(Local Socket)を使うと、同一のコンピュータ内だけでターミナルを共有することができます。別のコンピュータからは接続できなくなるので、多少セキュアです。

[driver@driver.local. ~/partty]$ partty host -l ~/socket
[driver@driver.local. ~/partty]$ chmod 660 ~/socket
[partnerr@driver.local. ~/partty]$ partty guest -l ~/socket

ターミナルサイズ

ターミナルの大きさはPartty Hostに合わせられます。GuestでHostよりも小さいターミナルを開いていると、途中で改行されて見にくくなってしまいます。Guestでターミナルを大きくするか、Hostでターミナルを小さくしてあげてください。


新機能

安定性向上

安定性がとても向上し、まともに使えるようになりました。

ネットワーク越しの共有に対応

UNIXドメインソケットに加えて、TCPでの通信に対応しました。ただ認証機能は無いので、注意してください。通信は平文です。

バイナリが一つ

Host側とGuest側が一つのバイナリになっているので、持ち運びや配布が簡単です。
第一引数にhostかguestか指定しないといけませんが、これは最初の1文字(あるいは2文字、3文字)を指定するだけでもOKです。たとえば以下のように指定することもできます。

[partner.local. ~/partty]$ ./partty gue driver.local:25770
柔軟なコマンドライン

コマンドライン引数パーサ「Kazuhiki」を搭載。

Guestを待ち受けるポート番号などの指定には、待ち受けるネットワークインターフェースを限定したり、1つのネットワークインターフェースに複数のIPアドレスが割り振られている場合には待ち受けるIPアドレスを指定することができます。
さらに、IPv6アドレスにも対応しています。IPv6アドレスでポート番号を指定するには、以下のように[ ]で囲って指定します。

[ipv6host]$ partty g [fe80:0000:0000:0000:0216:cbff:fe0f:e602%eth0]:5700

Kazuhiki - コマンドライン引数パーサ for C++

C/C++だと、コマンドライン引数の解析って面倒ですよね。正規表現が使えれば…しかし余分なライブラリに依存するのもいただけない。
getoptも、やっぱり面倒。結局文字列からint型に変換したり、変換できなかったらエラーを出したりは、自分でやらないといけない。


そこで、型変換まで面倒を見てくれるC++用のコマンドライン引数パーサ Kazuhiki を作りました。MITライセンスで公開します。
ダウンロード: kazuhiki-0.1


ヘッダをインクルードするだけで利用でき、濫用された演算子オーバーロードで構文を記述すると、あとは良きに計らってくれます。たとえば以下のように記述します。

bool cmd_number;
int num = 0;
unsigned int un = 0;
unsigned int hex = 0;
double d = 0;

struct sockaddr_storage ss;    // IPv4/v6両対応

bool use_string;
std::string str;

using namespace Kazuhiki;

RootParser parser;
parser  << IgnoreOne()    // argv[0]はプログラム名
        << CommandParser()
                % ( CommandParser::Command("number", cmd_number)
                        << OptionParser()
                                % NumericKey(false, "number", "n", num)    // 符号付き整数
                                % NumericKey(false, "unsigned", "u", un)    // 負の数ならエラー
                                % NumericKey(true,  "hex", "h", hex, std::hex)    // 必須オプション, 16進数で指定
                                % NumericKey(false, "double", "d", d)    // 小数, 指数指定
                  )
                % ( CommandParser::Command("network")
                        << SwitchParser()
                                % ( SwitchParser::Switch("-s", use_string)    // "-s"なら
                                        << SingleString(true, str)    // 文字列を1つ受け取る(必須)
                                  )
                                % ( SwitchParser::Other()    // それ以外なら
                                        << SingleFlexibleActiveHost(true, ss, 12345)    // デフォルトポート番号1235番で良きに計らう
                                  )
                  )
                ;

try {
        parser.parse(argc, argv);
} catch ( const ArgumentError& ex ) {
        std::cout << ex.what() << std::endl;
        exit(1);
}

templateを多用しており、引数に渡した変数の型によって適切に変換してくれます。変換できなかったら、エラー(例外)を出してくれます。


CommandParserは、Parttyのように第一引数によってその後に取る引数が変わる場合に使います。コマンドはCommandParser::Commandをoperator%で渡して列挙します。operator<<よりoperator%の方が優先順位が高いことに注意してください。

コマンド名は途中までしか指定されていなくても受け付けます。たとえばこの場合、"nu"と指定されていれば"number"、"ne"と指定されて入れば"network"と判断します。"n"だけだとどちらか分からないので、エラーになります。このときのエラーメッセージは「ambiguity command "n" ( number network )」になります。


ほとんどの機能は基底クラスで定義されており、それを継承して新しいクラスを定義することで、独自の引数形式を作ることもできます。たとえば、指定された引数の1文字目を取り出すクラスは、以下のように定義できます。

////
// First Character
//
class FirstCharacter : public WriteOnce {
public:
        SingleString(bool required, char& val) :
                WriteOnce(required),
                m_val(val) {}
public:
        unsigned int write_value(int argc, const char* const argv[]) {
                m_val = argv[0][0];
                return 1;    // 引数を1つ消費
        }
private:
        char& m_val;
};


ちなみにParttyの引数パーサは、以下のようになっています。

std::string prog;

bool mode_host;
bool mode_guest;

bool use_local;
std::string local_path;

struct sockaddr_storage addr;

{
        using namespace Kazuhiki;

        RootParser parser; 
        parser  << IgnoreOne()
                << CommandParser()
                        % ( CommandParser::Command("host", mode_host)
                                << SwitchParser()
                                        % ( SwitchParser::Switch("-l", use_local)
                                                << SingleString(true, local_path)
                                          )       
                                        % ( SwitchParser::Other()
                                                << SingleFlexiblePassiveAddress(addr, DEFAULT_PORT)
                                          )
                          )

                        % ( CommandParser::Command("guest", mode_guest)
                                << SwitchParser()
                                        % ( SwitchParser::Switch("-l", use_local)
                                                << SingleString(true, local_path)
                                          )       
                                        % ( SwitchParser::Other()
                                                << SingleFlexibleActiveHost(true, addr, DEFAULT_PORT)
                                          )
                          )
                        ;

        try {
                parser.parse(argc, argv);
        } catch( const ArgumentError& ex ) {
                std::cerr << ex.what() << std::endl;
                usage();
                exit(1);
        }
}