daily dayflower

2008-08-05

MVPen (Pegasus Mobile NoteTaker) の解析 (1)

MVPenLinux から使う試みシリーズです。

※注意※ MVPen が故障する可能性があります

MVPen のレシーバのデバイス構造

MVPen のレシーバを PC に接続すると,下記のような構成になります。

f:id:dayflower:20080805095402p:image

つまり,

の2つからなる USB 複合デバイスであることがわかります。

前者の「HID 準拠デバイス」が,レシーバをコントロールする(レシーバに格納されているデータを問い合わせたりする)ためのインタフェースです。後者の「HID 準拠マウス」が,オンラインマウスモードでのインタフェースになります。後者の「HID 準拠デバイス」がおそらくオンラインノートモードでのインタフェースなのではないかと思われますこれは「HID 準拠スタイラスペン」のようです。詳しくは MVPen (Pegasus Mobile NoteTaker) の解析 (3) - daily dayflower を参照してください。

USB ヒューマンインタフェースデバイス (HID) とは

キーボード入力,マウス入力などマンマシンインタフェースを司る機器につけられる USB クラスです。

USB クラスについては詳しくは Wikipedia の記事を参照してください。HID クラスのうち,キーボードマウスゲームパッドのように既知のもの(規格上すでに決まっているもの)については,おのおの専用のドライバOS によって組み込まれますが,既知でないものについても generic な HIDドライバが読み込まれアクセスすることができます。上図で「HID 準拠デバイス」となっているのは,「既定のものではないけど汎用 HID デバイス*1としてアクセスできるよ」という意味になります。


そう,MVPen が基本的に HID 準拠デバイスだったことは幸いでした。もし独自のデバイスクラスのものであれば各 OS 用にドライバを書く必要がありますが,HID デバイスであるためドライバは各 OSWindows しかり MacOS X しかり Linux しかり)にもともと用意されています。デバイスの情報を取得するには,このドライバ経由でアクセスすればすむわけです。


実際,MVPen に限らず単純な入出力インタフェースが必要な USB デバイスHID デバイスとして作られることも多いようです。特別なドライバを開発する必要がないため,開発工数が少なくなりますからね。

USB パケットを解析するには

USB に流れるパケットはブロードキャスト,だそうです。ですからそれを sniff したりブリッジとして挟んだりすることによってパケットを解析する(ハードウェアUSB Protocol Analyzer が存在します。

Windows の場合 USB の上位ドライバフィルタドライバを挟み込むことで,純ソフトウェア的に sniff することができます。オープンソースウェアとして USB Sniffer for Windowsスクリーンショット例として USB sniffer for Windows 98, 98SE, 2000 and Windows XP とか)というものもありますし,商業製品もいくつかあるようです。

USB Sniffer for Windows を使ってもよかったのですが,今回は

という製品*2を使いました。USB Sniffer for Windows に比べてユーザフレンドリーなところ*3と,インストール再起動の必要がないというお手軽なところが決め手でした*4インストールして 15 日以内なら無償で試用できます。


実際の解析手順は,レシーバを PC に接続して USB Monitor を起動,あとは NoteTaker ソフトウェアを立ち上げてメモの取り込みなどをすれば USB Monitor 側にパケットの中身が表示されます。

HIDUSB パケットをやりとりするには

Delphi 用に HID デバイスをあやつるための HIDKomponent というコンポーネントがあります。この配布の中にサンプルとして SimpleHIDWrite というソフトウェアが含まれており, HID パケットのやりとりを行うことができます。残念ながらビルド済みバイナリは配布物に含まれていませんが, LUISAVIAROMA - LUXURY SHOPPING WORLDWIDE SHIPPING - FLORENCE の Basics 節にある「SimpleHIDWrite」というリンクからバイナリダウンロードすることができます。

アクティブな sniff はできない(と思う)のですが,接続してボタンを押したりしたときなどのパッシブな sniff はできます。また,後日紹介するパケット解析結果がわかっていれば,このソフトウェアだけで一通りためす(制御コードを送ったり,レポートを受信したり)ことができます。

参考文献

*1:「HID デバイス」と書くと馬から落馬なのですが,見やすくわかりやすいのであえてそう表現しています。

*2日本語化されたものもあるのですが,今回は英語版を使いました。

*3:といいつつ知識がないとなかなかわかりにくいのですが。

*4USB Sniffer for Windows再起動は必要ありませんが,フィルタドライバをメニューからインストールする必要があります。HDD USB Monitorバックグラウンドで自動的にそういうことをやってくれるのかな。

2008-06-02

リモートデスクトップ接続でキーボードレイアウトを再初期化するプログラム

わたしは英語キーボードを使っているのですが,Linux からリモートデスクトップ接続したときに,入力ロケールが EN になってしまって困っていました。

-k en-us のように英語キーマップを指定するとうまくいく……のですが,ログイン時に Windows 側の「入力言語」が「英語」になってしまいます(ログインダイアログに「EN」と表示される)。入力言語は Alt + Shift キーを押すと切り替えることができます。

一応,コントロールパネルの「地域と言語のオプション」から「言語」タブの「テキストサービスと入力言語」で「詳細」ボタンを押し,「既定の言語」が「英語(米国) - US」になっているので「Microsoft Natual Input 2002」を選択すればいいはず……ですが,ログインするたびに再び EN になってしまうのが困り者です。

rdesktop の tips - daily dayflower

つまり,-k en-us のように英語キーマップを指定するとキーボードの表記と入力される文字が一致する一方,入力ロケールが英語になるので IME がそのままでは(Alt + Grave を押しても)使えない,と。Alt + Shift を押して入力ロケールを切り替えることができるけど,いちいちそれをするのがめんどい。

これわたしのとこだけの特殊な状況かなと思ってたら,フランス語と英語の両キーボードを使ってる人も困っていたのでほっと?しました。


んで,いちいちコンパネ立ち上げて直すのも大変なのでちょっとしたコードを書いてみました。

#include <windows.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

static void ShowErrorMessageBox(DWORD dwError = 0);

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	LSTATUS	r;
	HKL		hkl;
	TCHAR	layoutId[32];
	DWORD	dwRegType, cbLayoutId;

	if (! GetSystemMetrics(SM_REMOTESESSION))
		return 0;

	cbLayoutId = sizeof(layoutId);

	r = SHRegGetValue(HKEY_CURRENT_USER, TEXT("Keyboard Layout\\Preload"), TEXT("1"),
	                  SRRF_RT_REG_SZ, &dwRegType, layoutId, &cbLayoutId);
	if (r != ERROR_SUCCESS) {
		ShowErrorMessageBox();
		return 1;
	}
#if 0
	MessageBox(NULL, layoutId, TEXT("Layout ID"), MB_OK | MB_ICONINFORMATION);
#endif

	hkl = LoadKeyboardLayout(layoutId, KLF_SUBSTITUTE_OK | KLF_ACTIVATE);
	if (! hkl) {
		ShowErrorMessageBox();
		return 1;
	}

	if (! SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &hkl, SPIF_SENDCHANGE)) {
		ShowErrorMessageBox();
		return 1;
	}

	return 0;
}

static void
ShowErrorMessageBox(DWORD dwError /* = 0 */)
{
    /* ほげほげ……ほんとは FormatMessage() して MessageBox() を出してるけど略 */
}

ポイントは,

  • レジストリではローカルログオン時の入力ロケールの優先順位が HKCU\Keyboard Layout\Preload 以下にあるんだけど,リモートデスクトップ接続するとなぜかシステム的にオーバーライドされる。なので,レジストリから入力ロケールを読み取って SystemParametersInfo(SPI_SETDEFAULTINPUTLANG) で再設定してる。これでうまくいく。
  • GetSystemMetrics(SM_REMOTESESSION) で 0 以外(一応 1)が帰ると,それはリモートデスクトップ接続した環境。0 だとローカルログインした環境。これが一番ポータブルで安全で楽な確認手段。
  • HKL をそのまま SystemParametersInfo() に渡していてハマりました。実際は HKL のさらにポインタ
  • SHRegGetValue() は deprecated で RegGetValue() 使いなよ,と最近の SDK に書いてあるけど,使えるのは XP 64bit 以降(Vista 含む)。なので,あえて使ってます。EXPAND_SZ とかにも対応できるのでこれ系の関数を使うと楽。

レイアウトコードの長さが(ゆとりはみてるものの)決め打ちなのがいまいちですが,めんどいのでこれで。


注意点としては,これを実行した後に起動するプロセスに対してのみ入力ロケールの変更が効くところです。つまり,スタートアップとかに仕掛けても,たとえばそれ以前に起動したプロセスには効いていないし,エクスプローラ自身にも効いていません。まぁ,エクスプローラ自身は Alt + Shift で対処してください。

2008-05-28

Windows XP のリモートデスクトップに rdesktop でフルカラー接続

rdesktop(ターミナルサーバクライアント等のラッパも含む)で Windows XPリモートデスクトップに接続すると 16 bit color になってしまうのは rdesktop のせいだと思っていたのですが,rdesktop 自身はフルカラーで接続することができたんですね。(少なくとも私の環境では)Windows XP 側の設定によって色解像度を落とされていたみたいです。

に解決策が載っていました。

2008-05-29 追記: グループポリシーオブジェクトエディタを手っ取り早く立ち上げるには,%WINDIR%\system32gpedit.msc をダブルクリックすればいいみたいです。……もっと簡単には「ファイル名を指定して実行」で「gpedit.msc」と入力する。

  • 「ファイル名を指定して実行」から「mmc」と入力して管理コンソールを立ち上げる。
  • コンソールルートに何も読み込まれていないので「ファイル」メニューから「スナップインの追加と削除」メニューを選択する。
  • 開いたダイアログの下部の「追加」ボタンを押す。
  • 「グループ ポリシー オブジェクト エディタ」を選択して「追加」ボタンを押す。
  • ダイアログの「OK」ボタンを押す。
  • 左側ツリーペインを「コンソール ルート」「ローカル コンピュータ ポリシー」「コンピュータの構成」「管理用テンプレート」「Windows コンポーネント」「ターミナル サービス」の順にたどっていく。
  • 設定「色の解像度を制限する」をダブルクリックする。
  • 「未構成」になっている場合,「有効」を選択して「色の解像度」から「24 ビット」を選択して「OK」を押す。
  • スナップインの状態は別に保存しなくても構わない。

やっぱ RDP はいいよー

2008-05-09

Boodler で環境音を BGM に

ごくごく私的用事で環境音を延々と流す必要にせまられました。そういえば以前ためしてガッテンで環境音を聴いていると集中が続きやすいという話もありましたね。そのような環境音を生成する Web ページやシェアウェア*1,既製の素材もあるようです*2

ともかく,フリーウェアでそのようなニーズに答えられるものはないものか,と探していたら ピンクノイズを発生させる | OSDN Magazine に答がありました。Boodler というソフトウェアです。

Boodler とは

Boodler is a tool for creating soundscapes -- continuous, infinitely varying streams of sound.

Boodler Home

Boodler がしてくれることを簡単にまとめると,さまざまな sound snippets をシームレスかつダイナミックにつなぎあわせて sound stream を生成すること,です(デフォルトで停止させない限り延々と再生しますし,ファイルに落とし込むこともできます)。

Boodler は C で書かれたサブモジュールと Python モジュールで構成されています。このため Linux(や *BSD) はもちろん,MacOS XWindows でも動きます。ただし素敵な GUI はついておらず,コマンドラインから操る必要があります。

Boodler は下記の3つのコンポーネントにわかれます。

  • Python と C で書かれたコアモジュール(Boodler-*.tar.gzsrc/ 以下)
  • 環境音ストリームを生成する agent(Boodler-*.tar.gzeffects/ 以下)
  • 素材となる sound snippets(boodler-snd-*.tar.gz

Boodler をインストールWindows 編)

Python で書かれているので http://www.python.org/ より Windows 用インストーラ(現時点で Version 2.5.2)をダウンロードしてインストールしておきます。インストーラが *.pyプログラムとして認識するようにしてくれるみたいなので,特に PATH を設定する必要はありません。

次に Boodler 本体を取得します。http://eblong.com/zarf/boodler/ の「Get Boodler」から「download newer package for Windows」というリンクを見つけてダウンロードします。これを(どこでもいいですが)例えば C:\App\boodler などのディレクトリに展開しておきます。

この配布物は Python 2.5 用ではないようなので,「download cboodle module for Windows Python 2.5」というリンクから Python 2.5 用モジュールを取得して置き換えます。README.txt に手順が書いてありますが,C:\App\boodler\boodle\cboodle.dll を削除して代わりに cboodle.pyd を置きます。

最後に Boodler sound library を取得します。「download Boodler sound library」というリンクからダウンロードします。展開すると boodler-snd というディレクトリがあるので,これをアプリケーションのフォルダ内に移動します(先ほどの例だと C:\App\boodler\boodler-snd)。

ここまでで,C:\App\boodler フォルダの中身は以下のようになっているかと思います。

  • boodle/
  • boodler-snd/
  • doc/
  • effects/
  • boodler.py
  • boomsg.py
  • setpath.bat
  • 他雑多なファイル

Boodler を実行するには,コマンドプロンプトを開いて C:\App\boodler ディレクトリに移動し,環境変数を setpath.bat で設定します。しかるのちに boodler.py コマンドを実行します。

C:\Documents and Settings\dayflower> cd C:\App\boodler

C:\App\boodler> setpath.bat

C:\App\boodler> boodler.py cricket.CricketMeadow
running "Texas Meadow Katydid and friends"

虫の音が聴こえてきたら成功です。「Ctrl+C」で止めます。


昨日家でここまで試して満足したので,以下は Linux でテストしました。

Boodler をインストールLinux 編)

Ubuntu 8.04 で試しました。

Python が必要なのは当たり前ですが,ビルドするためにはヘッダやライブラリが必要となります。python2.5-devインストールしておきます。

ほか,あるとうれしいライブラリ。

  • libesd0-dev
  • liblame-dev
  • libogg-dev
  • libvorbis-dev

libogg-devlibvorbis-devlibshout3-devインストールすると依存で自動的に入りますからそれを入れると楽です。

んで,setuptools に対応してるので python setup.py build, sudo python setup.py installビルドインストールできます。ですが,これは上で述べたコアモジュールしかインストールしてくれないので,どうせなら,ということでユーザ環境にインストールしてみます。

$ mkdir tmp && cd tmp

$ tar zxf ~/Boodler-1.6.1.tar.gz

$ cd Boodler-1.6.1

$ python setup.py build

$ python setup.py install --prefix ~/bin/Boodler

ホームディレクトリの bin/Boodler 以下にインストールしてみました。他に必要となる effect agents や sound library を同所にコピーします。

$ cp -R effects ~/bin/Boodler/

$ cd ~/bin/Boodler

$ tar zxf ~/boodler-snd-021902.tar.gz

$ ls -F

$ bin/ boodler-snd/ effects/ lib/

どうせ BOODLER_SOUND_PATHBOODLER_EFFECTS_PATH などの環境変数の設定が必要となるので起動用シェルスクリプトを作ります。

$ vi ~/bin/boodler.sh

$ vi ~/bin/boomsg.sh

$ chmod a+x ~/bin/boodler.sh ~/bin/boomsg.sh

それぞれの内容は,

#!/bin/sh
# --- boodler.sh

APPPATH=~/bin/Boodler

export BOODLER_SOUND_PATH=$APPPATH/boodler-snd
export BOODLER_EFFECTS_PATH=$APPPATH/effects

PYTHONPATH=$APPPATH/lib/python2.5/site-packages \
    python $APPPATH/bin/boodler.py $*
#!/bin/sh
# --- boomsg.sh

APPPATH=~/bin/Boodler

PYTHONPATH=$APPPATH/lib/python2.5/site-packages \
    python $APPPATH/bin/boomsg.py $*

こんな感じにしました。

ためしに実行してみます。

$ ~/bin/boodler.sh pwrain.Rainforest
running "rain in the rainforest"

沢のある森にいる気分になってきました?

Boodler を使ってみる

コマンドラインで指定している引数は agent を指定するためのものです。先ほどの例だと pwrain.Rainforest という引数でしたが,これは pwrain というパッケージの Rainforest というクラスで実装されているのです。

どのような agent があるのか確認してみます。

$ cd ~/bin/Boodler/effects

$ grep -e '^class ' *.py

blop.py:class BlopEchoes(Agent):
blop.py:class BlopSpace(Agent):
blop.py:class TapEchoes(Agent):
blop.py:class TapSpace(Agent):
blop.py:class OccasionalGong(Agent):
blop.py:class TonkEchoes(Agent):
blop.py:class TonkSpace(Agent):
blop.py:class EchoWorld(Agent):
cavepool.py:class Drip(Agent):
cavepool.py:class Still(Agent):
cavepool.py:class Water(Agent):
...... snip snip snip ......

たくさんあります。

それぞれがどのような音に対応するのかは Boodler: Catalog of Soundscapes に記述されています(Utilities, Tools, and Managers はとりあえず無視してください)。

環境音にかぎらず,こんな風変わりな agent もあります。

$ ~/bin/boodler.sh timespeak.Now
running "speak the current time"

英語で時刻を喋り,終了します。

複数のストリームを再生する

effect agent には音を生成するだけではなく,他の agent を制御するものもあります。

一番よく使うのは manager パッケージの agents です。

たとえば,manager.Simultaneous agent は複数の agent を同時に立ち上げます。つまり複数の soundscape streams を合成して再生してくれるわけです。

$ ~/bin/boodler.sh manager.Simultaneous frogs.FrogPond pwrain.RainSounds
running "start several agents simultaneously"

雨のなか,蛙が鳴いているさまが再現されます。

manager.Simultaneous は複数 stream を並列して同時に再生しましたが,順番に再生していく agent もあります。manager.Sequential がそうです。

$ ~/bin/boodler.sh manager.Sequential 5 10 frogs.FrogPond pwrain.RainSounds
running "cycle among several agents"

最初の二つの引数で,遷移する時間間隔(秒)を指定します。この例では5秒〜10秒です。きちんとクロスフェードしてつないでくれます。

Boodler server を立ち上げる

Boodler は他のプロセスからのメッセージを受信して動作を変更するサーバモードも備わっています。

サーバモードで立ち上げるにはコマンドラインにて -l オプションを指定します。また,メッセージ受信用の agent は listen パッケージにいろいろ備わっています。

機能が細かく分化しているので先ほどの manager.Simultaneous を併用してサーバを立ち上げます。

$ ~/bin/boodler.sh -l manager.Simultaneous listen.Agents listen.TimeSpeak listen.Shutdown
running "start several agents simultaneously"

待ちに入りましたので,別プロセスからメッセージを送ってみます。

$ ~/bin/boomsg.sh time

現在時刻を喋りました(listen.TimeSpeak が受信)。

$ ~/bin/boomsg.sh agent crows.SomeCrows

カラスが鳴いています(listen.Agents が受信)。

$ ~/bin/boomsg.sh agent fire.Bonfire

焚き火の音にクロスフェードしました。

$ ~/bin/boomsg.sh shutdown

サーバプロセスが終了しました(listen.Shutdown が受信)。


Boodler には shoutcast 用出力も用意されているので,この listen agent と組み合わせれば,BGM 環境として使うためには別に素敵な GUI はいらない気もしてきます。

$ sudo apt-get install icecast2

$ sudo icecast2 -b -c /etc/icecast2/icecast.xml

で Icecast2 サーバを立ち上げてあげて*3

$ ~/bin/boodler.sh -o shout owstorm.RainForever

で Icecast2 サーバに Boodler からつなぎ,ブラウザで http://localhost:8000/ を見ると mount point が表示されているので,こいつをクリックすると動画プレーヤで再生できます。この例では localhost で完結させましたが,なにか開いたサーバにこれらをつっこんでおけば環境音 BGM サーバのできあがり,というわけです。

ライセンスにご注意

Boodler 本体は LGPL なのですが,sound library がパブリックドメインとは限らないことに注意が必要です。

The sound files in the Boodler sound library are not all in the public domain. Most of them are licensed "for private and non-commercial use only". Some were found by random searching around the web, and appear without any copyright statement at all.

Boodler: Licensing

個人・非商用で使うには問題ないものが多いそうですが。

*1リラックスして眠りやすい音楽を作る「Sound Sleeping」 - GIGAZINE

*2先日、NHKの試してガッテンで、集中するのに役立つ「環境音」と… - 人力検索はてな

*3:デフォルトの /etc/icecast2/icecast.xml の設定がまずいので <changeowner>usergroupicecast2icecast に書き換えて有効にする必要があります

2008-02-22

噂の SeamlessRDP を動かしてみた

f:id:dayflower:20080222184314j:image

環境は

  • クライアント: rdesktop 1.5.0 (ディストリビューション付属) on Fedora 8
  • サーバ: Windows XP Pro SP2 on VMware Server 1.0.3 on CentOS 5.1

です。

で,元記事( SeamlessRDPでXPとLinuxをシームレスに統合 (1/3) - ITmedia エンタープライズ)のとおりにやってみたんですが……

2008年02月08日 kunumi 仮想化, linux, windows, vmware fedoraのパッケージ持ってきてやっているんだが、普通にリモートの画面が開くだけで解説の様にはできないorz。

おさがしのページは見つかりませんでした

あるある〜。最初同じようにはまりました。


で,どうも調べたところ,SeamlessRDP を使うには,ゲスト Windows XP 側で下記要件が必要らしいです(⇒ Ubuntu Forums)。

  • 「ようこそ画面を使用する」が enable になっている
  • 「ユーザーの簡易切り替えを使用する」が enable になっている
  • Windows ファイアウォールで「例外」として「リモート デスクトップ」が許可されている

上2つは,「コントロールパネル」の「ユーザー アカウント」の「ユーザーのログオンやログオフの方法を変更する」から変更できます。

つ,つまり,コンピュータが「ドメイン」に属していると無理です。

また,実用的に使用するためには,ウィンドウズ起動時に「開始するにはユーザ名をクリックしてください。」というメッセージとともに,ユーザ一覧が表示されている必要があります。どういうことかというと,たとえばインストール直後にユーザーが1人しか登録されておらず,パスワードを特に設定していないと,起動時に「ようこそ」って勝手にログインしてしまいますよね。なので,

  • ユーザとして2人以上登録しておく
  • ユーザにパスワードを設定する

のいずれかをしておく必要があります。

でも,どうせ空パスワードのユーザってデフォルトではリモートデスクトップでアクセスするのは許可されてないんですよ*1。ですから,一番簡単なのは,アクセスするユーザにパスワードを設定しておくことです。


あと,インストールについてですが,SeamlessRDP を展開して C:\Windows\system32 にすべて放り込んでおきました。パスが通っているところに置くと楽なので。また,各ファイルを「ブロックの解除」しておくことをお忘れなく(see http://support.microsoft.com/kb/884237/ja)。

例題 1: notepad を起動する

以上の条件をうまく満たされている場合。

% rdesktop -A -s 'seamlessrdpshell notepad' 192.168.xxx.xxx -u user -p password

とすると,最初真っ黒で大きなウィンドウが立ち上がり*2,自動ログインしてそれが閉じたあとに,メモ帳のアプリケーションが立ち上がります。

特徴は,

  • あたかもルートレスウィンドウのようですが,装飾は Windows 側で行っています
  • ちょっと残念なことに,Luna などの縁取りが丸いウィンドウボーダー部分は透過しません
  • ウィンドウのリサイズはうまく作動します
  • ウィンドウのドラッグもまあまあうまく動きますが,ちょっとぎこちないです
  • ウィンドウの最大化をするとおかしくなるのでしないほうが身のためです
  • ウィンドウの最小化は問題がないようです

101 keyboard を使っているのでデフォルトではキー操作だけでうまく IME を on にできませんでした。おそらく秀 Caps などで他のキーにバインドするとうまくいくと思います。106 key だとどうでしょう。うまくいくんじゃないかなぁ。

この状態でも Windows PE のようにメモ帳を shell として使うことができます(参考⇒ 管理者必携! 最強のデータ・サルベージ・ツールを自作する (5/5):無償入手可能なミニWindows OS、Windows PE 2.0実践活用術 - @IT)。つまり,「ファイル」→「開く」でファイルオープンダイアログを開き,「ファイルの種類」を「すべてのファイル」にして,実行ファイルを選択。右クリックで「開く」を選択すると,新しいアプリケーションがやはりルートレスウィンドウで開くのです。


ここで,

% rdesktop -A -s 'seamlessrdpshell C:\Program Files\Internet Explorer\IEXPLORE.EXE' \
            192.168.xxx.xxx -u user -p password

のように,別のコマンドを実行すると,最初に実行していたコマンドが終了しますが,Windows 側のアプリはそのままになります。今回の例だと,IE は開かず,代わりにもともと開いていたメモ帳がアクティブになるだけです。

んで,元記事にも書いてありますが,ここでメモ帳を終了しても Windows にはセッションログインしたままになっています。これを解消するには,VMware Server Console でつなぎ,ユーザを選択してパスワードを入力するとログインされます。ところがシェルとして Explorer が立ち上がっていないので,背景の壁紙が表示されているのみです。で,ここで「Ctrl+Alt+Ins」を押すと*3,タスクマネージャが立ち上がるので,メニューの「シャットダウン」から「ユーザのログオフ」を選ぶと,無事セッションを終了できます。BK, BK。

このへんは,SeamlessRDP の Windows 側サーバコンポーネントがソース公開されているので,がんばればいろいろ工夫できそうかなぁ。

例題 2: explorer を起動する

Open Tech Press の記事のとおりでうまくいきましたので,詳しくは説明しません。

% rdesktop -A -s 'seamlessrdpshell explorer' 192.168.xxx.xxx -u user -p password

でうまくいきました。またこの方法だと,Explorer のメニューから「ユーザのログオフ」を選ぶと,セッションも終了するのが good です。

いまどきの GNOME だとメニューが上下両方にあるので,Windows のタスクバーと重なってしまうのがネックですね。

あと Explorer 以外の shell(GeoShell とか BB4Win とか)だと使用感はどうなんでしょうね。面白そう。

で,どうなの?

普段,

  • Fedora 8 をデスクトップ環境で使用
  • Windows を使用する必要があるときは,VMware Player で Windows guest を立ち上げる

という作業環境です。

それに比べてどうかというと,やはりルートレスウィンドウで動かせるのは便利そうだし,面白いかな,と思います。

あと,VMware Server Console は VNC プロトコルで画面転送してるんですよね(VMware Workstation / Player については謎)。で VNC に比べると RDP プロトコルはやはり軽いなぁーという印象です。それに,マウスカーソルやフォーカスの切り替えが(VMware Tools をインストールした環境と比べても)シームレスでいいなぁ,と思います。

問題は,ゲスト自身の実行パフォーマンスがどうか,ですね。VMware Server 上で動かすのと,VMware Workstation ベースの VMware Player で動かすのは一応パフォーマンスが違うはずですから。これはわかりません。

rdesktop の利点
  • SeamlessRDP だとルートレスウィンドウとしてアプリケーションを立ち上げられる
  • マウスカーソルやフォーカスの切り替えがシームレス
  • 画像転送プロトコルとして RDP はやはり軽い気がする
rdesktop の欠点
  • SeamlessRDP だとウィンドウのハンドリングがやや不安定な印象がある(ドラッグ時や最大化時など)
  • SeamlessRDP だとゲストマシンをドメインに属させることができないのが痛い

*1:「コントロールパネル」→「管理ツール」→「ローカル セキュリティ設定」→「ローカル ポリシー」→「セキュリティ オプション」→「アカウント: ローカル アカウントの空のパスワードの使用をコンソール ログオンのみに制限する」を「無効」にすれば,パスワードのないアカウントでもリモートデスクトップを使用することができます。

*2:もし -u, -p オプションを指定していないと,このフェーズでユーザ名とパスワードを問うダイアログが開きます

*3:VMware Server Console は,Ctrl+Alt+Delete の代わりに Ctrl+Alt+Ins を使うのです。Delete でも一応伝わりますが,危険なので Ins を使うようにしましょう

2008-02-18

breakttc を Perl で書いてみた

TTC ファイルを分割するためには BREAKTTC.EXE という Microsoft 製のソフトウェアがあるのですが,今は公開されてないようですし,いちいち Windows を立ち上げるのも面倒です。TTC の構造については仕様が公開されているので Perl で書いてみました。

non seekable なストリームから読み込みできるようにするために汚いコードになってしまってます。

分割するだけなら楽なんですよね。結合するのは,各テーブルの内容を比較しないといけないので面倒。まして non seekable stream だと事実上無理じゃないかなぁ。

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use POSIX qw( ceil );
use File::Spec::Functions qw( :ALL );

my %OPTION;
GetOptions(
    'help|h|?'   => \$OPTION{help},
    'verbose|v'  => \$OPTION{verbose},
    'quiet|q'    => \$OPTION{quiet},
    'template|t' => \$OPTION{template},
    'index|i'    => \$OPTION{index},
) or die 'Bad options';

if (! $OPTION{template}) {
    if (@ARGV) {
        my (undef, undef, $filename) = splitpath($ARGV[0]);
        $filename =~ s{ [.] .*? \z }{}xmso;
        $OPTION{template} = "${filename}_%02d.ttf";
    }
    else {
        $OPTION{template} = 'font_%02d.ttf';
    }
}

my $handle;
if (@ARGV) {
    open $handle, '<', $ARGV[0]
        or die $!;
    binmode $handle;
}
else {
    $handle = \*STDIN;
}
binmode $handle;

my $ttc_header = read_ttc_header($handle);
my $numFonts = $ttc_header->{numFonts};

my $src_headers = [];
for my $i (0 .. $numFonts - 1) {
    my $offset = tell $handle;
    my $table_offset = $ttc_header->{OffsetTable}->[$i];

#   seek $handle, $table_offset - $offset, 1  or  die $!;
    skip_handle($handle, $table_offset - $offset);

    $src_headers->[$i] = read_header($handle);
}

my $tables = merge_tables($src_headers);

my $dst_headers = [];
for my $i (0 .. $numFonts - 1) {
    $dst_headers->[$i] = trans_header($src_headers->[$i]);
}

my $dst_handles = [];
for my $i (0 .. $numFonts - 1) {
    my $filename = sprintf $OPTION{template}, $i + 1;

    open my $h, '>', $filename
        or die "open: $!";
    binmode $h;

    $dst_handles->[$i] = $h;

    write_header($dst_handles->[$i], $dst_headers->[$i]);
}

copy_bodies_for_fonts($dst_handles, $handle, $tables);

for my $i (0 .. $numFonts - 1) {
    close $dst_handles->[$i];
}

close $handle;

exit;

sub read_ttc_header {
    my ($src)  = @_;
    my $header = {};
    my $buf;

    read $src, $buf, 4
        or die "read: $!";
    $header->{TTCTag} = $buf;

    die "not TTC file"
        unless $header->{TTCTag} eq 'ttcf';

    read $src, $buf, 4
        or die "read: $!";
    ($header->{versionLow},
     $header->{versionHigh})
        = unpack 'nn', $buf;
    $header->{Version} = $header->{versionLow} . '.' . $header->{versionHigh};

    die "unknown version: " . $header->{Version}
        if $header->{Version} != 1.0
        && $header->{Version} != 2.0;

    print {*STDERR} "TTC Version: ", $header->{Version}, "\n"
        if $OPTION{verbose};

    read $src, $buf, 4
        or die "read: $!";
    $header->{numFonts} = unpack 'N', $buf;

    print {*STDERR} "TTC numFonts: ", $header->{numFonts}, "\n"
        if $OPTION{verbose};

    for my $i (0 .. $header->{numFonts} - 1) {
        read $src, $buf, 4
            or die "read: $!";
        $header->{OffsetTable}->[$i] = unpack 'N', $buf;
    }

    $header->{OffsetTable} = [ sort @{ $header->{OffsetTable} } ];

    if ($header->{Version} >= 2.0) {
        read $src, $buf, 4+4+4
            or die "read: $!";
    }

    return $header;
}

sub read_header {
    my ($src)  = @_;
    my $header = {};
    my $buf;

    read $src, $buf, 4  or  die $!;
    $header->{version} = $buf;

    read $src, $buf, 2+2+2+2  or  die $!;
    ($header->{numTables},
     $header->{searchRange},
     $header->{entrySelector},
     $header->{rangeShift})
        = unpack 'nnnn', $buf;

    my $numTables = $header->{numTables};

    $header->{table} = {};
    $header->{tags}  = [];

    while ($numTables -- > 0) {
        read $src, $buf, 4+4+4+4  or  die $!;
        my $tag = substr $buf, 0, 4, '';
        my ($checkSum, $offset, $length)
            = unpack 'NNN', $buf;

        $header->{table}->{$tag}
            = {
                tag      => $tag,
                checkSum => $checkSum,
                offset   => $offset,
                length   => $length,
            };

        push @{ $header->{tags} }, $tag;
    }

    return $header;
}

sub trans_header {
    my $header = clone_header($_[0]);

    $header->{entrySelector}
        = ceil(log $header->{numTables} / log 2);

    $header->{searchRange}
        = (1 << $header->{entrySelector}) * 16;

    $header->{rangeShift}
        = $header->{numTables} * 16
          - $header->{searchRange};

    $header->{tags}
        = [
            sort {
                $header->{table}->{$a}->{offset}
                    <=>
                $header->{table}->{$b}->{offset}
            }
            @{ $header->{tags} }
          ];

    my $length
        = 4+2+2+2+2 + (4+4+4+4) * $header->{numTables};
    $header->{length} = ceil($length / 4) * 4;
    $header->{trail}  = $header->{length} - $length;

    my $offset = $header->{length};
    foreach my $tag (@{ $header->{tags} }) {
        my $table = $header->{table}->{$tag};

        $table->{offset} = $offset;

        $offset += $table->{length};
        $offset += 3 - ($offset + 3) % 4;
    }

    return $header;
}

sub clone_header {
    my ($src) = @_;
#   use Storable qw( dclone );
#   return dclone($_[0]);

    my $dst = { %$src };

    $dst->{tags} = [ @{ $src->{tags} } ];

    $dst->{table} = {};
    while (my ($tag, $table) = each %{ $src->{table} }) {
        $dst->{table}->{$tag} = { %$table };
    }

    return $dst;
}

sub write_header {
    my ($dst, $header) = @_;

    print {$dst} $header->{version};

    print {$dst}
        pack 'nnnn', $header->{numTables},
                     $header->{searchRange},
                     $header->{entrySelector},
                     $header->{rangeShift};

    foreach my $tag (@{ $header->{tags} }) {
        my $table = $header->{table}->{$tag};

        print {$dst} $tag;
        print {$dst}
            pack 'NNN', $table->{checkSum},
                        $table->{offset},
                        $table->{length};
    }

    print {$dst} "\x00" x $header->{trail};
}

sub merge_tables {
    my ($headers) = @_;

    my $table_by_ofs = {};
    my $i = 0;
    foreach my $header (@$headers) {
        foreach my $tag (@{ $header->{tags} }) {
            my $table = $header->{table}->{$tag};
            $table_by_ofs->{$table->{offset}}->{table} = $table;
            push @{ $table_by_ofs->{$table->{offset}}->{id} }, $i;
        }
        $i ++;
    }

    my @tables = map  {
                    {
                        %{ $table_by_ofs->{$_}->{table} },
                        id => $table_by_ofs->{$_}->{id},
                    } 
                 }
                 sort { $a <=> $b }
                 keys %$table_by_ofs;

    return \@tables;
}

sub copy_bodies_for_fonts {
    my ($dst_handles, $src_handle, $tables) = @_;

    my $offset = tell $src_handle;

    foreach my $table (@$tables) {
#       seek $src_handle, $table->{offset} - $offset, 1  or  die $!;
        skip_handle($src_handle, $table->{offset} - $offset);
        $offset = $table->{offset};

        my $data = read_handle($src_handle, $table->{length});
        $offset += $table->{length};

        my $trail = 3 - ($table->{length} + 3) % 4;
        $trail = "\x00" x $trail;

        foreach my $i (@{ $table->{id} }) {
            my $dst_handle = $dst_handles->[$i];

            print {$dst_handle} $data, $trail;
        }
    }
}

sub skip_handle {
    my ($src, $length) = @_;
    my $buf;
    my $unit = 4096;

    die "cannot seek backward: $length"
        if $length < 0;

    while ($length > 0) {
        $unit = $length if $length < $unit;

        read $src, $buf, $unit  or  die $!;

        $length -= $unit;
    }
}

sub read_handle {
    my ($src, $length) = @_;
    my $data;
    my $buf;
    my $unit = 4096;

    while ($length > 0) {
        $unit = $length if $length < $unit;

        read $src, $buf, $unit  or  die $!;
        $data .= $buf;

        $length -= $unit;
    }

    return $data;
}

2007-10-24

Delphi で暗号化するなら CAPICOM を使うと楽

Delphi に限った話でもないですが。

win32 で DES 等の暗号化をしようと思った場合,Windows に標準で付属している Crypto API を使うと,外部ライブラリ等を自力でコンパイルする必要がありません。この Crypto API の使い方はトラスト・ソフトウェア・システムさんの文書(簡単な暗号化 - 共通鍵暗号化)に詳しく書いてあります。

ですが,Wincrypt.h というの,Delphi 6(等)には標準で付属していないんですよね。FreePascal の h2pas を使って必要な部分だけコピペしてあれこれやろうとしていたんですが,この API を使うこと自体も面倒くさい。

と思って Windows SDK の文書を眺めていたら,Crypto API だけでなく,CAPICOM というものがあるそうな。C(rypto)API COM interface ……つまり Crypto API が COM オブジェクト化されてるらしいです。おお,これなら Delphi とかから使うの楽そうだ。

ということで,使ってみました。といっても CAPICOM の使い方はとっても簡単。VBScript の例ですが,no title のような記事もあります。

Delphi で CAPICOM を使うには,

  • 「タイプライブラリの取り込み」で「CAPICOM v2.1 Type Library」を取り込む
  • コンポーネントラッパーの作成」はどちらでもよい。ですが,私は Delphi チックに TEncryptedData 等々使いたかったのでチェックしておきました。
  • ビジュアルコンポーネントがあるわけではないので,プロジェクトのフォルダにもってきて取り込んでよい(念のため Register プロシージャを削除)
  • ユニットの initialization 節に CoInitialize(nil); を,finalization 節に CoUninitialize(); を追加

な手順を踏めば OK。

実際使用するコードの例ですが,拙いものを下記においときます。

uses CAPICOM_TLB;

var
  enc: TEncryptedData;
  tut: TUtilities;
begin
  enc := TEncryptedData.Create(Self);
  try
    enc.Algorithm.Name      := CAPICOM_ENCRYPTION_ALGORITHM_DES;
    enc.Algorithm.KeyLength := CAPICOM_ENCRYPTION_KEY_LENGTH_128_BITS;
    enc.SetSecret('secret', CAPICOM_SECRET_PASSWORD);

    enc.Content := MemoDecrypted.Text;

    tut := TUtilities.Create(Self);
    try
      MemoEncrypted.Text := tut.BinaryToHex(enc.Encrypt(CAPICOM_ENCODE_BINARY));
    finally
      tut.Free();
    end;
  finally
    enc.Free();
  end;
end;

Algorithm プロパティをあれこれ操作していますが,特に設定しなくてもデフォルト設定のまま動きます。

また,デフォルトで CAPICOM.EncryptedData::Encrypt() メソッドは Base64 なデータを返すので,サンプルの意味もこめて,CAPICOM.Utilities オブジェクトの BinaryToHex を使用して hex で取得しています*1

Crypto API のように細かい調整はかけにくいですが,お手軽に使うのなら CAPICOM でいいんではないでしょうか。


問題は CAPICOM オブジェクトがどの Windows において標準で登録されているか,ですが,わかりません。一応 Redistributable なコンポーネントではあります。

2007/11/02 追記

標準で登録されてるかどうかについて。

んー条件がよくわからないですが,

という結果でした。

どうも,We are sorry, the page you requested cannot be found ことにより,Microsoft Update した機種だとインストールされるっぽい(⇒CAPICOM が突如搭載されることになった)です。

なお(少なくとも)上記のパッチを手動でインストールすると,システムフォルダではなくて Program Files のあるフォルダにインストールされます。その理由についてはこちらのブログ参照ってことで。

で,SDK Redistributable ですが,

ここからダウンロードできるのですが,表記は 2.1.0.1 になってますが,中身は 2.1.0.2 (脆弱性対策済)でした。

*1:無論 VCL 標準の BinToHex 関数を使ってもいいんですが,サンプルとしての意味合いとして