☆ドライバのソースを変更した場合、バイナリの何処が変わるか調べてみました

前回の ☆バイナリファイルを書き換えてUSB無線LANクライアントを認識させる は、depmodが調べているセクション、シンボルをとりあえず書き換えてみただけです。
この2箇所の書き換えは、必要ではありますが、必要十分であるかは不明です。
本当はUSB無線LANクラアントのカーネルモジュールが備えるべきインターフェースの仕様がわかればよいのですが、検索してもそのものズバリは見つけられませんでした。

2010/06/01 追記
USB_DEVICEマクロを使ってIdVendor,IdProductの指定をしている
("__mod_usb_device_table"のmatch_flagsが0x03)
場合は前回の方法で問題ないとおもわれますが、match_flagsの値がそれ以外の場合は十分でないようです。
hitoさんによる此方の投稿を参照してください。
https://forums.ubuntulinux.jp/viewtopic.php?pid=60947#p60947

そこでカーネルモジュールのソースの箇所だけUSBデバイス認識用の"idProduct"を書き換え、ビルドしてバイナリファイルの何処が変わったか比較してみることにしました。
RT2870_LinuxSTA_V2.3.0.0.tar.tar.bz2を使用して検証します。(これは私の環境で正攻法で動かなかったものなので、材料としては若干不安なのですが。)
結果を先に書くと、前回のバイナリ書き換えはおそらく正解(必要十分)であろうと思われます。

比較用のモジュールをビルドする際は同一のパスでビルドする方が良いです。
".rodata.str1.4"にはパスを保持している箇所があって、比較の際にそこが差分として出てくるとかなり面倒です。
パスの長さが違うとファイルサイズやシンボルのファイル先頭からのオフセットも違ってくる可能性がありそうです。

●ソースコードの変更箇所(および自動的に変更された箇所)

■common/rtusb_dev_id.c

下記がソースを手で変更する唯一の箇所です。

--- a/common/rtusb_dev_id.c 2009-11-26 15:22:40.000000000 +0900
+++ b/common/rtusb_dev_id.c 2010-05-30 21:14:50.800580320 +0900
@@ -59,7 +59,7 @@
  {USB_DEVICE(0x0DF6,0x002D)}, /* Sitecom */
  {USB_DEVICE(0x14B2,0x3C06)}, /* Conceptronic */
  {USB_DEVICE(0x14B2,0x3C28)}, /* Conceptronic */
- {USB_DEVICE(0x2019,0xED06)}, /* Planex Communications, Inc. */
+ {USB_DEVICE(0x2019,0xED14)}, /* Planex Communications, Inc. */
  {USB_DEVICE(0x07D1,0x3C09)}, /* D-Link */
  {USB_DEVICE(0x07D1,0x3C11)}, /* D-Link */
  {USB_DEVICE(0x14B2,0x3C07)}, /* AL */

■os/linux/rt2870sta.mod.c

RT2870_LinuxSTA_V2.3.0.0.tar.tar.bz2では自動で変更されます。
MODULE_INFOの部分が変わるのは実施前に想定していませんでした。

--- a/os/linux/rt2870sta.mod.c 2010-05-30 21:17:10.616580922 +0900
+++ b/os/linux/rt2870sta.mod.c 2010-05-30 21:19:10.388580922 +0900
@@ -151,7 +151,7 @@
 MODULE_ALIAS("usb:v0DF6p002Dd*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v14B2p3C06d*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v14B2p3C28d*dc*dsc*dp*ic*isc*ip*");
-MODULE_ALIAS("usb:v2019pED06d*dc*dsc*dp*ic*isc*ip*");
+MODULE_ALIAS("usb:v2019pED14d*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v07D1p3C09d*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v07D1p3C11d*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v14B2p3C07d*dc*dsc*dp*ic*isc*ip*");
@@ -198,4 +198,4 @@
 MODULE_ALIAS("usb:v100Dp9031d*dc*dsc*dp*ic*isc*ip*");
 MODULE_ALIAS("usb:v0DB0p6899d*dc*dsc*dp*ic*isc*ip*");
 
-MODULE_INFO(srcversion, "BFE72125F5BC16053AC2BDE");
+MODULE_INFO(srcversion, "F9CA397B948AAF46EC1F3E8");

●バイナリが変わった箇所

"cmp -l"を使用してカーネルモジュールが変わった位置を調べました。
出力は下記の通りでした。

"cmp -l"はreadelfやodと違いファイルの先頭バイトの位置が"1"になっているので、"cmp -l"で出た差分をodで調べる際はオフセットの値に注意が必要です。
値は8進数でダンプされます。

そのままでは見にくいので、連続する領域の先頭アドレスと長さ、該当するセクションに変換すると下記の通りです。

start   length  Section
    68  20      .note.gnu.build-i
486964   1      .rodata.str1.1
491211  23      .modinfo
494162   2      .modinfo
520852   1      .data

cmp -l の出力
69  53  51
    70 324 343
    71 114 131
    72 302 122
    73 262 135
    74 153 213
    75 225 136
    76 260 221
    77 334 157
    78 243  75
    79 213 135
    80 141 361
    81 154 145
    82  17 350
    83  52 224
    84   6 232
    85 147 104
    86 342  21
    87 215 246
    88 102 253
486965  67  71
491212 102 106
491213 106  71
491214 105 103
491215  67 101
491216  62  63
491217  61  71
491218  62  67
491219  65 102
491220 106  71
491221  65  64
491222 102  70
491223 103 101
491224  61 101
491225  66 106
491226  60  64
491227  65  66
491228  63 105
491229 101 103
491230 103  61
491231  62 106
491232 102  63
491233 104 105
491234 105  70
494163  60  61
494164  66  64
520853   6  24

■ソース修正が反映されるバイナリの箇所
▼".modinfo"セクションのalias

"elfdump -p '.modinfo'"の出力の差分だけ示します。(-uをつけないdiff)
srcversionの変更は予想していなかったものですが、ソースの変更がそのまま反映されています。
下記の2箇所に相当します。

start   length  Section
491211  23      .modinfo
494162   2      .modinfo

9c9
<   [    e0]  srcversion=BFE72125F5BC16053AC2BDE
---
>   [    e0]  srcversion=F9CA397B948AAF46EC1F3E8
55c55
<   [   c60]  alias=usb:v2019pED06d*dc*dsc*dp*ic*isc*ip*
---
>   [   c60]  alias=usb:v2019pED14d*dc*dsc*dp*ic*isc*ip*

▼".data"セクションの"__mod_usb_device_table"シンボル

前回と同じ要領で"__mod_usb_device_table"シンボルをダンプした結果の差分だけ示します。
下記の箇所に相当します。

start   length  Section
520852   1      .data

21c21
< 0520848 0003 2019 ed06 0000 0000 0000 0000 0000 0000 0000
---
> 0520848 0003 2019 ed14 0000 0000 0000 0000 0000 0000 0000

■変わると想定していなかったが変わったバイナリの箇所
▼".note.gnu.build-i"セクション
start   length  Section
    68  20      .note.gnu.build-i

このセクションは こちらに よると
"ファイルを識別する為のユニークなビットフィールド"が埋め込まれているようです。
20Byteの長さにわたって差分が出たので160-bit SHA1 hash だと思われます。
ひょっとしたら改ざんを検出するためにこの部分をチェックするシステムも存在するのかもしれませんが、
前回のエントリで動いていたのでUbuntuはチェックしていないと思われます。

▼".rodata.str1.1"セクションのビルドした時刻
start   length  Section
486964   1      .rodata.str1.1

"readelf -p '.rodata.str1.1'"でセクションの内容を見てみると、主にメッセージに使用される文字列などが格納されているようです。
変更前後のセクションのダンプを比較すると下記の様になっていました。
ビルドしたタイムスタンプが格納されている部分で違っています。
ここはバイナリを直接書き換える際に修正しなくても問題なさそうです。
@@ -968,7 +968,7 @@
 
   [  246b]  ConnStatus is not connected
 
-  [  2488]  21:17:00
+  [  2488]  21:19:00
   [  2491]  May 30 2010
   [  249d]  2.3.0.0
   [  24a5]  Driver version-%s, %s %s

☆バイナリファイルを書き換えてUSB無線LANクライアントを認識させる

このエントリに記載している方法は危険かもしれません。実施される方は自己責任で行ってください。

2010/06/01 追記
USB_DEVICEマクロを使ってIdVendor,IdProductの指定をしている
("__mod_usb_device_table"のmatch_flagsが0x03)
場合はこの方法で問題ないとおもわれますが、match_flagsの値がそれ以外の場合は十分でないようです。
hitoさんによる此方の投稿を参照してください。
https://forums.ubuntulinux.jp/viewtopic.php?pid=60947#p60947

Ubuntuでインストール後にすぐ使用できるコマンドだけでカーネルモジュールを直接書き換えて
USB無線LANクライアントを認識させる方法を下記に記述します。
(readelf,od,strings,perlおよびテキストエディタを使用)

成功例が一つ(rt2870sta)だけであるため、別のモジュールで有効であるか不明ですが、
どうしても有線LANの接続が確保できない場合やビルド環境の導入が困難である場合に
試してみる価値はあると思います。

使用するUSB無線LANクラアント(GW-USMicroN)がLucid,Karmicで
デフォルトで認識されてしまうので例はJauntyで実施しました。
カーネルは2.6.28-11-generic。GW-USMicroNのIDは2019:ed14です。
これだけでは心もとないので、Lucidで2019:ed14を取り除いたモジュールをビルドし、同じ要領を実施しました。
これも認識、接続に成功しています。
実はRalinkのドライバ(RT2870_LinuxSTA_V2.3.0.0.tar.tar.bz2)で試そうと思ったのですが、なぜかソースに2019:ed14を追加してビルドした場合でもGW-USMicroNで接続が出来ませんでした。(Lucid,Karmicとも)

前提条件は対象のUSB無線LANクライアントが使用しているチップを駆動するカーネルモジュールが既知で
あること。(そしてデフォルトでモジュールが存在するが、子機が認識されないこと。)
USB無線LAN子機のidVendor,idProductのペアがmodules.alias,modules.usbmapに
存在しない場合、下記の方法で接続できる可能性があります。

目次

●1.前置き(きっかけ)

●2.readelf,od,stringsで収集する情報

●3.書き換え作業

●4.モジュールの認識



●1.前置き(きっかけ)

きっかけはdepmodコマンドで更新される[/lib/modules/$(uname-r)/]のmodules.* にlsbusbで出力されるidVendor,idProductと同じ値を見つけたことに始まります。

当該の値が含まれているファイルはmodules.aliasとmodules.usbmapで下記のようなものでした。

■modules.aliasの内容
(先頭行と一部抜粋)
# Aliases extracted from modules themselves.
alias rt3070sta rt2870sta
alias usb:v0411p015Dd*dc*dsc*dp*ic*isc*ip* rt2870sta
alias usb:v1737p0077d*dc*dsc*dp*ic*isc*ip* rt2870sta
■modules.usbmapの内容
(先頭行と一部抜粋)
# usb module         match_flags idVendor idProduct bcdDevice_lo bcdDevice_hi bDeviceClass bDeviceSubClass bDeviceProtocol bInterfaceClass bInterfaceSubClass bInterfaceProtocol driver_info
rt2870sta            0x0003      0x148f   0x2770    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0
rt2870sta            0x0003      0x1737   0x0071    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0
rt2870sta            0x0003      0x1737   0x0070    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0
rt2870sta            0x0003      0x148f   0x2870    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00               0x0

これらの値はそれぞれdepmodコマンドがモジュールのファイルから読み出したものです。
depmodのソースから調べてみると下記の部分から取得していました。

■modules.aliasの元情報

モジュールの ".modinfo" セクションを元に作成されています。

$ readelf -p .modinfo モジュールファイルのパス
で簡単に見ることができます。

モジュールのソースにおていこの情報は
linux/module.hで定義されているMODULE_ALIASマクロを使って宣言されているようです。

#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)

#define MODULE_DEVICE_TABLE(type,name)  \
  MODULE_GENERIC_TABLE(type##_device,name)

■modules.usbmapの元情報

モジュールの"__mod_usb_device_table"シンボルに格納されています。
バイナリなので容易には見ることができません。
depmodを含むmodule-init-toolsのソースパッケージではtables.hで
その構造とサイズを定義しています。
(usb_device_id構造体、サイズはUSB_DEVICE_SIZE32,USB_DEVICE_SIZE64)

モジュールのソースにおていこの情報は
linux/usb.hで定義されている下記のマクロを使って宣言されているようです。

#define USB_DEVICE(vend,prod) \
 .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
 .idVendor = (vend), \
 .idProduct = (prod)

Ralinkのドライバ(RT2870_LinuxSTA_V2.3.0.0.tar.tar.bz2)では
USB_DEVICEを使用しているソースからMODULE_ALIASを使用するソースを
自動で生成しているようです。

●2.readelf,od,stringsで収集する情報

書き換え対象のidVendor,idProductとそのモジュール上の位置を知る必要があります。 ここでは"__mod_usb_device_table"シンボルの先頭のIDを対象にします。 そして対応する".modinfo"のエントリのオフセットを探します。

■モジュールの退避、バックアップの作成

対象モジュールを作業ディレクトリにコピーし、念のためバックアップを作っておきます。

$ sudo cp -p /lib/modules/$(uname -r)/kernel/drivers/staging/rt2870/rt2870sta.ko ./
$ TRG_MOD=./rt2870sta.ko
$ sudo cp -p $TRG_MOD ${TRG_MOD}.org
■"__mod_usb_device_table"の最初のエントリの位置と値の取得

まずreadelfコマンドで"__mod_usb_device_table"シンボルがが格納されている
セクションとその先頭からのオフセット、サイズを取得します。
コマンドの実行結果から25番目のセクションに格納されていて、
先頭からのオフセットが16進で0x4140、サイズが1220Byteであることが分かります。
(先頭だけを対象にするのでサイズは特に必要ありません。)

$ TRG_MOD=/lib/modules/2.6.28-11-generic/kernel/drivers/staging/rt2870/rt2870sta.ko
$ readelf -s $TRG_MOD | sed -ne '1,3p;/__mod_usb_device_table/p;'

Symbol table '.symtab' contains 1267 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
   555: 00004140  1220 OBJECT  GLOBAL DEFAULT   25 __mod_usb_device_table

25番目のセクションを探します。".data"セクションです。
ファイルの先頭から16進で0x6c980で始まっていることが分かります。
(別のモジュールで試してみると番号[Nr]はモジュールによって違いますが、
セクション名は".data"でした。基本的に".data"セクションを探せばよさそうです。)

$ readelf -S $TRG_MOD | sed -ne '1,4p;/\[25\]/p;'
There are 35 section headers, starting at offset 0x73830:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [25] .data             PROGBITS        00000000 06c980 004694 00  WA  0   0 32

開始アドレスを計算してodコマンドで内容を表示します。
1レコードが20Byteで先頭5項目はunsigned shortであることが分かっているので
(depmodのソースtable.h参照)
odのオプションに -N 20 --width=20 -t x2 を指定します。

ここで -t x1 とすると、x86はリトルエンディアンなのでunsigned shortの
上位バイトと下位バイトが逆になって表示されます。
出力から書き換え箇所はファイル先頭からのオフセットが十進で(461504 + 2) Byte
そこからunsigned shortを2つ分(計4byte)を書き換え対象にします。

$ perl -e 'print 0x4140 + 0x6c980 ,"\n"'
461504
$ od -j 461504 -N 20 -v -A d -t x2 --width=20 $TRG_MOD
0461504 0003 148f 2770 0000 0000 0000 0000 0000 0000 0000
■modules.aliasの元になる情報の書き換え位置

次にmodules.aliasの元になる情報の書き換え位置を調べます。
これは文字列なのでstringsコマンドに-tdオプションを指定することで
比較的簡単に調べることができます。
十進でファイルの先頭からのオフセット438048Byte,長さ20Byteを書き換えます。
("alias=usb:v148Fp2770" を "alias=usb:v2019pED14"へ)

$ strings -td $TRG_MOD | grep -i -e 148f.2770
 438048 alias=usb:v148Fp2770d*dc*dsc*dp*ic*isc*ip*

●3.書き換え作業

perlを使って書き換えを行ないます。

■"__mod_usb_device_table"の先頭書き換え

write_vp.plというスクリプトを書きました。
ネット接続不可の状況でも、その場ですぐにかけるような内容です。
書換え後のidVendor,idProductは直書きです。
引数は対象ファイルとオフセットです。

### スクリプト内容の表示
$ cat ./write_vp.pl
#!/usr/bin/perl

if($#ARGV != 1){
    print STDERR "usage $0 filepath offset\n";
}

my($file) = shift;
my($offset) = shift;

open FH, "+< " . $file or die "$!:$file";
seek(FH, $offset, 0);
my($buf) = pack "SS", 0x2019, 0xed14;
syswrite(FH, $buf, 4) or die "$!";

### 実行
$ sudo ./write_vp.pl $TRG_MOD 461506
### 確認
$ od -j 461504 -N 20 -v -A d -t x2 --width=20 $TRG_MOD
0461504 0003 2019 ed14 0000 0000 0000 0000 0000 0000 0000
■".modinfo"セクションの書き換え

write_alias.plというスクリプトを書きました。
write_vp.plと同じく短いものです。

### スクリプト内容の表示
$ cat ./write_alias.pl
#!/usr/bin/perl

if($#ARGV != 1){
    print STDERR "usage $0 filepath offset\n";
}

my($file) = shift;
my($offset) = shift;

open FH, "+< " . $file or die "$!:$file";
seek(FH, $offset, 0);
syswrite(FH,"alias=usb:v2019pED14",20);
seek(FH, $offset, 0);
sysread(FH,$buf,20);
print $buf,"\n";

### 実行
$ sudo ./write_alias.pl $TRG_MOD 438048
alias=usb:v2019pED14
### 確認
$ strings -td $TRG_MOD | grep -e 2019.ED14
 438048 alias=usb:v2019pED14d*dc*dsc*dp*ic*isc*ip*

●4.モジュールの認識

モジュールを正式な位置に書き戻してdepmodで依存情報を更新し、modprobeでモジュールを読み込みます。

$ sudo cp $TRG_MOD /lib/modules/$(uname -r)/kernel/drivers/staging/rt2870/
$ sudo depmod -a $(uname -r)
$ sudo modprobe rt2870sta

あとはNetworkManagerで認証関係の設定を行えば接続できるはずです。

☆HDDに置いたISOからブートしてUbuntuをインストールする

このエントリに記載している方法は危険かもしれません。実施される方は自己責任で行ってください。

vine_userさんのサイトでHDDに保存したISOイメージをGRUB2で直接起動する方法《Ubuntu 9.10対応》を見ました。
これはとても快適です。

調子に乗って、この方法をつかってブートし、UbiquityでLucidのインストールを試みたのですが、
実際にインストールを開始する段階の最初でアンマウントできない旨のメッセージが出てインストールできません。

悔しいので、気合を入れてUbiquityのソースを見てみたのですが、結構複雑で追いかけるのはかなりきついです。
基本的にPythonなのですが、GUIがある上に裏でシェルを使っていたりします。
そこで、エラーメッセージが出るところまで進めておいて、pstree -alp でそのときに走っているプロセスと
そのオプションを見てみました。関係ありそうな部分は下記です。

pstree -alp の一部分
|-gksudo,3123 --preserve-env --desktop /home/ubuntu/Desktop/ubiquity-gtkui.desktop -- /usr/bin/ubiquity gtk_ui
  |   `-udisks,3125 --inhibit -- /usr/lib/ubiquity/bin/ubiquity gtk_ui
  |       `-ubiquity,3128 /usr/lib/ubiquity/bin/ubiquity gtk_ui
  |           |-debconf-communi,4062 -w /usr/bin/debconf-communicate -fnoninteractive ubiquity
  |           |-ibus-daemon,4041 --xim
  |           |   |-ibus-gconf,4055
  |           |   |-python,4057 /usr/share/ibus/ui/gtk/main.py
  |           |   |-python,4060 /usr/share/ibus-anthy/engine/main.py --ibus
  |           |   `-{ibus-daemon},4056
  |           |-log-output,11685 -t ubiquity --pass-stdout /bin/partman-commit
  |           |   `-partman-commit,11686 /bin/partman-commit
  |           |       `-01unmount_busy,12462 /lib/partman/commit.d/01unmount_busy
  |           `-{ubiquity},11682

名前からして怪しいのが"/lib/partman/commit.d/01unmount_busy"です。
見てみると、確かにumountを実行しています。
GUIのエラーメッセージもここから出しているようで、そのおかげでpstreeの出力にも出てくれたみたいです。
debconfの機能を使ってumoutを実行するかどうかを調べているようです。
debconf経由でubiquity/partman-skip-unmountをtrueにするのがスマートなのでしょうが、
やりかたがわからなかったので下のコードのように処理を始める前に"exit 0"で正常終了するようにしました。
この修正を実施してからインストールを開始すればISOイメージとは別のパーティションにUbuntuをインストールできるようになります。
下図のダイアログが出たら、[Ubuntu 10.04 を試す]のボタンを選択していったんGnomeのデスクトップ環境を表示されて修正を行います。

立ち上がった直後はキーボードレイアウトに英語配列になっているので"Setting"->"Preferences"->"Keybord"で設定を日本語配列にしてから編集したほうが良いでしょう。

"/lib/partman/commit.d/01unmount_busy" の修正内容(わざわざdiffを表示するほどのものではないですが)
@@ -1,13 +1,13 @@
 #! /bin/sh
 
 # This should largely be unnecessary now that partman-base/init.d/parted
 # checks for mounted partitions at startup, but it may still serve as
 # insurance against partitions being mounted while the partitioner is
 # running.
-
+exit 0
 . /lib/partman/lib/base.sh
 
 db_get ubiquity/partman-skip-unmount
 if [ "$RET" = true ]; then
     exit 0
 fi

☆GmrunのキーバインドをBashに少しだけ近づける

私はGmrunが大好きです。
Mihai Bazonさん、すばらしいソフトウェアを作ってくれてありがとうございます。
さて、前回の「☆gmrun で Ctrl+PをUp、Ctrl+NをDownキーと同じように動作させる」に引き続き、
2つだけBashのキーバインドを付け加えてみました。
Ctrl+K、Ctrl+Uです。文字列を選択した状態はBashではありえない状態なので、
どういう仕様にするか迷ったのですがコーディングが楽な使用にしました。
パッチはこれです。
適用の手順は下記の通りです。
/usr/bin/gmrunだけ圧縮したアーカイブも用意しました。gmrun_0.9.1-4_i386.msll-patched.20100505.tar.gz
開発環境を入れたくない方はapt-getでgmrunを導入した後、/usr/bin/gmrunだけ入れ替えればOKです。
(ビルドしたパッケージをdpkgで導入すると更新がなくてもupdate-managerで
更新対象としてリストされるので、自分でビルドした場合でも一旦apt-getでgmrunを導入してから、
/usr/bin/gmrunだけ入れ替える方が良いかもしれません。
ちゃんとパッケージした方法で作れば良いのでしょうが、方法がわからず...)

ソース、パッチの取得とビルド
$ mkdir gmrun
$ apt-get source gmrun
$ sudo apt-get build-dep gmrun
$ wget http://sites.google.com/site/midspeclowload/files/gmrun-0.9.1-4.msll-20100505.patch?attredirects=0&d=1
$ patch -p1 < gmrun-0.9.1-4.msll-20100505.patch
$ cd gmrun-0.9.1/
$ debuild -r
gmrun-0.9.1-4.msll-20100505.patch
--- a/gmrun-0.9.1/src/gtkcompletionline.cc	2003-06-22 08:14:34.000000000 +0900
+++ b/gmrun-0.9.1/src/gtkcompletionline.cc	2010-05-05 22:14:05.296882925 +0900
@@ -975,6 +975,34 @@
       STOP_PRESS;
       return TRUE;
 
+     case GDK_K:
+     case GDK_k:
+      if (event->state & GDK_CONTROL_MASK) {
+        int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
+        int len = gtk_entry_get_text_length(GTK_ENTRY(cl));
+        gtk_editable_delete_text(GTK_EDITABLE(cl), pos, len);
+        gtk_editable_select_region(GTK_EDITABLE(cl), pos, pos);
+        if (MODE_SRC)
+          search_off(cl);
+        return TRUE;
+      } else goto ordinary;
+
+     case GDK_U:
+     case GDK_u:
+      if (event->state & GDK_CONTROL_MASK) {
+        int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
+        gtk_editable_delete_text(GTK_EDITABLE(cl), 0, pos);
+        gtk_editable_select_region(GTK_EDITABLE(cl), 0, 0);
+        if (MODE_SRC)
+          search_off(cl);
+        return TRUE;
+      } else goto ordinary;
+
+     case GDK_P:
+     case GDK_p:
+      if (event->state & GDK_CONTROL_MASK) {
+		;/* fall to 'case GDK_Up:'*/
+      } else goto ordinary;
      case GDK_Up:
       if (cl->win_compl != NULL) {
         int &item = cl->list_compl_items_where;
@@ -1010,6 +1038,11 @@
      }
      return FALSE;
 
+     case GDK_N:
+     case GDK_n:
+      if (event->state & GDK_CONTROL_MASK) {
+		;/* fall to 'case GDK_Down:'*/
+      } else goto ordinary;
      case GDK_Down:
       if (cl->win_compl != NULL) {
         int &item = cl->list_compl_items_where;

☆os-proberでsda10がsda2の前に出てくるのを直す

私はパーティションを制限いっぱいの16まで切っています。
これで困るのは、grub-updateで作成される/boot/grub/grub.cfgのmenuentryの順序です。
grub2のOS選択画面で/dev/sda2のUbuntuより先に/dev/sda10や/dev/sda11が先に表示されてしまいます。
30_os-proberのセクションではos-proberの出力順にしたがってmenuentryが作成されるので、
修正すべきはos-proberの出力順序です。

os-proberの中をのぞいてみるとpartitionsというシェル関数で"/sys/block/*/*[0-9]"という記述があります。
シェルのグロビングのソートは文字列の昇順なので、ここが原因だとわかります。
コマンドの性質上(障害時などは/usrがマウントされない可能性がある)、
スタティックリンクされた/bin,/sbinの下にあるバイナリだけを使用して処理を記述すべきです。
そうするとsortは使えません。もちろんawkやperlもダメです。
ちょっとしたパズル感覚です。

2010/05/22追記
スタティックリンクされたバイナリは/bin/busyboxと/sbin/ldconfig.realだけでした。
しかもbusyboxにはawkやsortも組み込まれています....

幸いsh(dash)のビルトインにprintfがあったので、sed,printf,シェルのグロビングのあわせ技でソートしてみました。
パッチはこれ os-prober.patch です。
tail_num_sortは/usr/share/os-prober/common.shに追加することも考えたのですが、 自分しか使わないと思ったのでos-proberに入れてしまいました。

しかし、Lucidになってからmenuentryの文字列が長すぎです。
OS選択画面でパーティションの部分が見えなくなってしまいました。
仕方がないので"e"を押してどのパーティションか確認してからCtrl+xで起動しています。

修正前のos-proberの出力
/dev/sda10:Ubuntu 10.04 LTS (10.04):Ubuntu:linux
/dev/sda11:Ubuntu 10.04 LTS (10.04):Ubuntu1:linux
/dev/sda2:Ubuntu 9.10 (9.10):Ubuntu2:linux
/dev/sda3:Ubuntu 9.10 (9.10):Ubuntu3:linux
/dev/sda5:Ubuntu 10.04 LTS (10.04):Ubuntu4:linux
/dev/sda6:Ubuntu 10.04 LTS (10.04):Ubuntu5:linux
/dev/sda7:Ubuntu lucid (development branch) (10.04):Ubuntu6:linux
/dev/sda8:Ubuntu 10.04 LTS (10.04):Ubuntu7:linux
/dev/sda9:Ubuntu 10.04 LTS (10.04):Ubuntu8:linux
修正後のos-proberの出力
/dev/sda2:Ubuntu 9.10 (9.10):Ubuntu:linux
/dev/sda3:Ubuntu 9.10 (9.10):Ubuntu1:linux
/dev/sda5:Ubuntu 10.04 LTS (10.04):Ubuntu2:linux
/dev/sda6:Ubuntu 10.04 LTS (10.04):Ubuntu3:linux
/dev/sda7:Ubuntu lucid (development branch) (10.04):Ubuntu4:linux
/dev/sda8:Ubuntu 10.04 LTS (10.04):Ubuntu5:linux
/dev/sda9:Ubuntu 10.04 LTS (10.04):Ubuntu6:linux
/dev/sda10:Ubuntu 10.04 LTS (10.04):Ubuntu7:linux
/dev/sda11:Ubuntu 10.04 LTS (10.04):Ubuntu8:linux
os-prober.patch
diff -ru a/os-prober b/os-prober
--- a/os-prober 2010-02-11 08:06:58.000000000 +0900
+++ b/os-prober 2010-05-04 21:45:47.261264519 +0900
@@ -24,12 +24,32 @@
  return 1
 }
 
+tail_num_sort() {
+ if [ $# -eq 0 ] ; then
+  return 0
+ fi
+
+ if type mktemp >/dev/null 2>&1; then
+  TAILNUMSORT_TMP="$(mktemp -d /tmp/tailnumsort.XXXXXX)"
+ else
+  TAILNUMSORT_TMP=/tmp/tailnumsort.$$
+ fi
+ trap "rm -rf $TAILNUMSORT_TMP" EXIT HUP INT QUIT TERM
+
+ for dev in $@ ; do
+  disk=$(echo $dev | sed -e 's#/##g;s#[0-9]*[0-9]*$##')
+  fname=$(printf '%s%02d' $disk $(echo $dev | sed -e "s#/##g;s#$disk##"))
+  echo $dev > $TAILNUMSORT_TMP/$fname
+ done
+ cat $TAILNUMSORT_TMP/* 2>/dev/null
+}
+
 partitions () {
  # Exclude partitions that have whole_disk sysfs attribute set.
  if [ -d /sys/block ]; then
   # Exclude partitions on physical disks that are part of a
   # Serial ATA RAID disk.
-  for part in /sys/block/*/*[0-9]; do
+  for part in $(tail_num_sort /sys/block/*/*[0-9]); do
    if [ -f "$part/start" ] && \
       [ ! -f "$part/whole_disk" ] && ! on_sataraid $part; then
     name="$(echo "${part##*/}" | sed 's,[!.],/,g')"

☆fsarchiverでパーティションをコピーしてみました

5/2 追記
5/1 当初の手順だと不足がありました。
grub.cfgのsearchの行とlinuxの行でUUIDの齟齬が生じて
結局 linux行が優先され、/dev/sda8をルートとして起動してしまいます。
修正した手順の確認がとれたので修正済みのものを掲載しました。
修正した点は、実行していたにもかかわらず記載していなかった7.update-grubの実行と
本質的に欠けていた6.のところのgrub.cfgの編集です。

Ubuntu 10.04 Lucid Lynxが正式リリースされました。
本格的に弄るのは日本語Rimixが出てからのつもりですが、一応インストールしました。
インストールすれば弄りたくなるのが人情です。
弄っておかしくなった場合や、バグ報告のを考慮すると、あまり弄っていない状態をすぐに作れるようにしておくと便利です。
パーティション丸ごとのバックアップといえばddやpartimageがメジャーですが、ddは敷居が高く、partimageはext4に対応していません。
そこでfsarchiverを使用してバックアップを作成し、これを別パーティションに復元して弄ることにしました。
fsarchiverはLucidからuniverseのリポジトリに入りました。バージョンはまだ0.6.8ですが...
幸いパーティションはMaxの16まで切ってあるので、空きはいっぱいあります。
とりあえず、/dev/sda8にdesktop-i386をインストール。
その後、sda9,sda10,sda11にコピーをしました。
(バックアップからのリストアは1つあたり3分くらいだったので調子に乗って3つもコピーしてしまいました。)

手順の概略
1.とりあえず10.04を/dev/sda8へインストール
2./dev/sda1にインストールしてあったKarmicを起動
3.fsarchiverで/dev/sda8を丸ごとバックアップ
4.fsarchiverで別のパーティション(/dev/sda9)へリストア
5.tune2fsでリストアしたパーティション(/dev/sda9)のUUIDを変更
6.リストアしたパーティション(/dev/sda9)に入っている設定ファイルを修正
7.リストアしたパーティションを起動時に選択可能にするためupdate-grubを実行
8.リブートして/dev/sda9から起動、dpkg-reconfigureでgrub-pcのインストール先を修正

(私の環境では"/home"や"/var"を分けずに"/"の1パーティションだけで運用しています。 "/home"は個別のパーティションにする理由は設定ファイルをシステムごとに別にするためです。 データ用のパーティションは別に用意して$HOMEのしたからシンボリックリンクを張っています。)

以下で3以降の手順を細かく説明します。

3.fsarchiverで/dev/sda8を丸ごとバックアップ

bzip2 -2と同一の圧縮レベルで、圧縮用のスレッドを2つ使用してバックアップ

$ sudo fsarchiver -z5 -j2 savefs sda8.fsa /dev/sda8

4.fsarchiverで別のパーティション(/dev/sda9)へリストア

$ sudo fsarchiver restfs sda8.fsa id=0,dest=/dev/sda9

5.tune2fsでリストアしたパーティション(/dev/sda9)のUUIDを変更

リストアしたパーティションのUUIDはバックアップしたパーティションと同じになっているので変更します。 tune2fsのmanページで見ると-Uのあとにrandomやtimeをしてできそうなのですが、なぜかうまくいきません。 普通にuuidgenの出力を使うことにしました。

$ sudo tune2fs -U $(uuidgen) /dev/sda9
# fstabを書き換えるためにUUIDを確認
$ sudo blkid /dev/sda9
/dev/sda9: UUID="2bd68985-c6cf-4eb5-9c55-aaee3b173656" TYPE="ext4" 

6.リストアしたパーティション(/dev/sda9)に入っている設定ファイルを修正

fstabとgrub.cfgを修正します。
grub.cfgの修正が必要な理由は、update-grubの実行時に走る/etc/grub.d/30_os-proberの中で
/usr/lib/linux-boot-probes/40grub2 が実行されるのですが、これがリストアしたパーティションにある
grug.cfgを参照してるからです。

$ sudo mkdir /mnt/sda9
# fstabのUUIDを書き換える。ついでにduring installationのコメントも/dev/sda9に変更
$ sudo vi /mnt/sda9/etc/fstab
$ sudo vi /mnt/sda9/boot/grub.cfg
fstabの修正内容
@@ -7,4 +7,4 @@
 #                
 proc            /proc           proc    nodev,noexec,nosuid 0       0
 # / was on /dev/sda10 during installation
-UUID=32c50640-99e1-49c1-837f-05701e9e7def /               ext4    errors=remount-ro 0       1
+UUID=2bd68985-c6cf-4eb5-9c55-aaee3b173656 /               ext4    errors=remount-ro 0       1
grub.cfgの修正内容(書込みは:w!で強制書込みをします)
UUIDの書き換えが必要な箇所は"### BEGIN /etc/grub.d/10_linux ###"から"### END /etc/grub.d/10_linux ###"の間にある
"linux"始まる行です。(ほかの部分は8.のdpkg-reconfigureの時に直ります。)
--- sda10_grub.cfg.org 2010-05-02 20:28:18.581936565 +0900
+++ sda10_grub.cfg.new 2010-05-02 20:31:53.980286836 +0900
@@ -63,22 +63,22 @@
 ### BEGIN /etc/grub.d/10_linux ###
 menuentry 'Ubuntu, with Linux 2.6.32-21-generic' --class ubuntu --class gnu-linux --class gnu --class os {
    recordfail
    insmod ext2
    set root='(hd0,8)'
    search --no-floppy --fs-uuid --set 32c50640-99e1-49c1-837f-05701e9e7def
-   linux   /boot/vmlinuz-2.6.32-21-generic root=UUID=32c50640-99e1-49c1-837f-05701e9e7def ro   quiet splash
+   linux   /boot/vmlinuz-2.6.32-21-generic root=UUID=2bd68985-c6cf-4eb5-9c55-aaee3b173656 ro   quiet splash
    initrd  /boot/initrd.img-2.6.32-21-generic
 }
 menuentry 'Ubuntu, with Linux 2.6.32-21-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os {
    recordfail
    insmod ext2
    set root='(hd0,8)'
    search --no-floppy --fs-uuid --set 32c50640-99e1-49c1-837f-05701e9e7def
    echo    'Loading Linux 2.6.32-21-generic ...'
-   linux   /boot/vmlinuz-2.6.32-21-generic root=UUID=32c50640-99e1-49c1-837f-05701e9e7def ro single 
+   linux   /boot/vmlinuz-2.6.32-21-generic root=UUID=2bd68985-c6cf-4eb5-9c55-aaee3b173656 ro single 
    echo    'Loading initial ramdisk ...'
    initrd  /boot/initrd.img-2.6.32-21-generic
 }
 ### END /etc/grub.d/10_linux ###
 
 ### BEGIN /etc/grub.d/20_memtest86+ ###

7.リストアしたパーティションを起動時に選択可能にするためupdate-grubを実行

$ sudo update-grub

8.リブートして/dev/sda9から起動、dpkg-reconfigureでgrub-pcのインストール先を修正

$ sudo dpkg-reconfigure grub-pc
2つほど設定を訊かれますがデフォルトで進めると下記のような画面が出るので grubのインストール先を選択しなおします。

☆LucidのGnome-Sudokuにパッチをあてる

Lucid(Ubuntu 10.04)のGnome数独(Gnome-sudoku 2.30)はKarmicで悪化した経過時間の表示が修正されています。
Karmicの時はセルの上側にメモ用の数字を入力すると経過時間がやたら増えたのですが、これも直っているようです。
(完全ではないようで、多少修正されています。)
しかし、残念ながら特定の場合にコンフリクトした値が赤くならないバグと終了判定が正しくないバグが残っています。
(すでにGnomeの新しいリビジョンでは修正されているので次のリリースでは修正されているはずです。)
これを修正するパッチを用意しました。

gnome-sudoku.2.30.0-0ubuntu6.patchをダウンロードして下記の要領でパッチしてください。
ファイルは ~/Download ディレクトリにダウンロードした前提で記述しています。

$ cd /usr/lib/python2.6/dist-packages/gnome_sudoku
$ sudo patch -p1 < ~/Download/gnome-sudoku.2.30.0-0ubuntu6.patch

注意点はgnome-games-commonとgnome-sudokuパッケージの両方にダブってsduoku.py,timer.pyが含まれていることです。
実際に使われているのはgnome-games-commonの方のsduoku.py,timer.pyですのでパッチを当てるファイルを間違わないように気をつけてください。

suoku.pyに対するパッチの内容はGnomeで実際に適用されたものではなく、私が書いたものです。
gitの使い方を多少学んだのですが、2.30からいくつかコミットが行われていてどれが該当するのかわからなかったのでorz.
(Gnomeで実際に適用されたパッチの方が良かった気がするのですが)
timer.pyの方の修正はGnomeで行われた修正を取り込みました。
こちらは変更が少ないのでgitで変更したコミットが簡単に特定でしました。

gnome-games$ find ./ -name 'timer.py'
../gnome-sudoku/src/lib/timer.py
gnome-games$ git blame -L '/^        if currently_timing:/,+1' -- ./gnome-sudoku/src/lib/timer.py
f235e03e (Jim Ross 2010-03-31 03:53:55 -0400 142)         if currently_timing:
gnome-games$ git log f235e03e^..f235e03e
commit f235e03e98e960a69627f5b4afacb9f14d599bad
Author: Jim Ross <jimbo@dimensia.com>
Date:   Wed Mar 31 03:53:55 2010 -0400

    sudoku: Fixed mark timing
    
    The problem is, when the main window is not active(timer paused) and
    the auto-save kicks in - the timer always gets restarted in mark_timing().
    
    This patch fixes it by storing the current value of self.is_timing at the st
    of mark_timing() and then checking it before calling resume_timing().
    
    Fixes bug #562782
gnome-games$ git show f235e03e
commit f235e03e98e960a69627f5b4afacb9f14d599bad
Author: Jim Ross 
Date:   Wed Mar 31 03:53:55 2010 -0400

    sudoku: Fixed mark timing
    
    The problem is, when the main window is not active(timer paused) and
    the auto-save kicks in - the timer always gets restarted in mark_timing().
    
    This patch fixes it by storing the current value of self.is_timing at the st
    of mark_timing() and then checking it before calling resume_timing().
    
    Fixes bug #562782

diff --git a/gnome-sudoku/src/lib/timer.py b/gnome-sudoku/src/lib/timer.py
index bd5e2de..c37511a 100644
--- a/gnome-sudoku/src/lib/timer.py
+++ b/gnome-sudoku/src/lib/timer.py
@@ -132,13 +132,15 @@ class ActiveTimer (gobject.GObject):
         self.resume_timing()
 
     def mark_timing(self):
+        currently_timing = self.is_timing
         self.pause_timing()
         if self.active_time < 1:
             self.active_time = 1
         # dirty hack: never let total time be less than active time
         if self.active_time > self.total_time:
             self.total_time = self.active_time
-        self.resume_timing()
+        if currently_timing:
+            self.resume_timing()
 
     def finish_timing (self):
         self.mark_timing()

☆LucidのNautilusに再びエンピツのトグルボタンを

Lucid(Ubuntu 10.04)のNautilus(2.30)では"text location bar"と"button bar"を切り替えるエンピツのトグルボタンがなくなってしまいました。
さるお方にアドバイスを頂いて、どなたかPPAで公開してくれる事を願ってPatchをLaunchPadに投稿したのですが、
今のところPPAにアップしてくれた方はいらっしゃらないようです。
ということで自分でパッケージをビルドしたものを公開してみます。
この投稿に提示した操作により損害が発生しても保障はいたしません。適用は自己責任でお願いいたします。

nautilus_2.30.0-0ubuntu4_i386.deb
nautilus_2.30.1-0ubuntu1_i386.deb

本来はパッケージ名を変えたりすべきなのですだと思うのですが、なにぶん良くわからないのでそのままです。
ダウンロードして下記のようにすれば、元々のパッケージと置き換えられます。

# ファイル名はバージョンに合わせて読み替えてください
$ sudo dpkg -i nautilus_2.30.1-0ubuntu1_i386.deb
$ pkill nautilus
※ログイン時にバックグラウンドでnautilusが起動されているのですが、これがいる限り新しいバイナリのnautilusは起動できません。

ソースからビルドしたい方はUbuntu Weekly Recipe 第16回を参照してビルドできる環境を整えてください。
ビルドできる環境が整ったら下記のようにしてビルドします。
正規の手続きではないと思うのですが、一応パッケージは作成されます。
(最後のほうで"running debsign failed"と出るのですが、パッケージは生成されています。)
ディレクトリの名称は適当に置き換えてください。 パスに日本語が含まれていると失敗するかもしれません。ディレクトリ名は英数字だけにしておくのが無難だと思います。

# ディレクトリ名はnautilusである必要はありません
$ mkdir nautilus
$ cd nautilus
# ソースの取得と展開
$ apt-get source nautilus
# ビルドに必要なパッケージの取得
$ sudo apt-get build-dep nautilus
# パッチのダウンロード
$ wget http://launchpadlibrarian.net/44697678/nautilus-2.30.0_add_location_togglebutton.patch
# ディレクトリはバージョンに合わせて読み替えてください。
$ cd nautilus-2.30.1/
# パッチの適用
$ patch -p1 < ../nautilus-2.30.0_add_location_togglebutton.patch
# ビルドの実行
$ debuild -r fakeroot

パッケージのメタ情報を何もいじらなかったせいか、更新がなくてもアップデートマネージャに更新対象として表示されてしまいます。
バージョンがまったく変わっていないときは手動でチェックを外さないと元に戻ってしまうみたいです。

☆Twitterのタイムラインをマージする

Twitterのタイムラインをマージしたいと思って、下記のようなスクリプトを書きました。
毎度初心者レベルなものですいません。
作った環境はUbuntu Karmic Koala。
libdatetime-format-dateparse-perl パッケージを導入していることが前提です。
導入した覚えがない場合は下記のようにして導入してください。
これを使う理由は☆Twitter の投稿時刻をPerlでローカル時刻にするを見てください。

$ sudo apt-get install libdatetime-format-dateparse-perl

tw_merge.shtw_merge_rev.pl をダウンロードして、実行権限をつけ、下記のようにして使います。
~/tmp というディレクトリを作るか、tw_merge.sh の HTML_TMP_DIR の指定を適当に変えてください。
スクリプトで作成したファイルは残ったままになるので、ときどき手動で削除してください。

使用例
$ tw_merge.sh _hito_ mizuno_as pores_n ikunya seotch henrich
$ tw_merge.sh 'cc_jp?max_id=11149920499&per_page=20' 'henrich?max_id=11149420427&per_page=24' 'Say_no?max_id=11150167435&per_page=4'
$ tw_merge.sh 'HPLinuxJP?max_id=11248010182&per_page=7' 'henrich?max_id=11192408823&per_page=2' 'henrich?max_id=11247481244&per_page=3'

ブラウザで結果が表示されます。
結果はTwitterのWeb表示とは逆順になります。
通常と同じ順番が場合はtw_merge_rev.plの下から4行目
「sort keys %TWEET」の部分を「reverse sort keys %TWEET」に修正してください。

最初の例の場合、リストと違って発言の多い人と少ない人でズレがでてきてしまうのが難点です。
揃えたい場合はper_pageでTweetの数を調整します。
古い時期の発言を揃えて見たい場合はmax_idを使います。

max_idやper_pageはあらかじめFirefoxで見たいところを選択して選択部分のソースをみて調べておきます。
(これが一番大変なのですが、簡単な方法が思いつきませんでした。)

一応リストもマージ対象に出来ます。 通常 user_name のところに user_name/list_name としてやればOKです。
(ちょっと表示がおかしくなりますが、読めないことはないです。)
リストが対象に入っている場合は、リストを引数の先頭にしてください。

tw_merge.sh
#!/bin/bash
HTML_TMP_DIR=~/tmp
OUT_HTML=${HTML_TMP_DIR}/TwitRev-`date '+%F-%H%M%S'`-$$.html

i=0
for arg in $*
do
  html=${HTML_TMP_DIR}/`echo $arg | sed -e 's#[/?&]#_#g'`.html
#  HTMLS="$HTMLS $html"
  HTMLS[$i]=$html
  wget -O - http://twitter.com/$arg |sed -e 's#</li>#</li>\n#;' > ${html}
  i=$(($i+1))
done

tw_merge_rev.pl ${HTMLS[*]} > $OUT_HTML
x-www-browser $OUT_HTML &
tw_merge_rev.pl
#!/usr/bin/perl
use DateTime;
use DateTime::Format::DateParse;
my $tzhere = DateTime::TimeZone->new( name => 'local' );

my $thumb_span_tmplt = <<'EOF';
<span class="thumb vcard author">
<a href="http://twitter.com/%s" class="tweet-url profile-pic url">
<img alt="%s" class="photo fn" height="48" src="%s" width="48" />
</a>
</span>
EOF


sub repTime
{
  my $gmt = shift;
  $dt = DateTime::Format::DateParse->parse_datetime($gmt);
  $dt->set_time_zone($tzhere);
}

my $is_header_saved = 0;
my $is_footer_saved = 0;
my (@HEADER, @FOOTER);
my (%TWEET);

for $twhtml (@ARGV){
    open TWHTML, $twhtml or die "$!:$twhtml\n";

    my $screen_name;
    my $url_screen_name;
    my $thumb_span;

    # search start of tweets.
    while(<TWHTML>){
      if(not $is_header_saved){
        s#<head>#<head><base href="http://twitter.com/" />#;
        push @HEADER, $_;
      }
      last if m#<ol id='timeline' class='statuses'>#;

      # build screen_name span
      if(!$screen_name and m#<meta content="(.+)" name="page-user-screen_name" />#){
        $screen_name = $1;
        $url_screen_name = qq(<strong><a href="http://twitter.com/$1">$1</a></strong>);
      }

      # buid thumb span
      if(!$thumb_span and m#<a href="/account/profile_image/.*src="([^"]+)"#){
        my $src_url = $1;
        $src_url =~ s#_bigger\.(\w+)$#_normal.\1#;
        $thumb_span = sprintf $thumb_span_tmplt, $screen_name, $screen_name, $src_url;
      }
    }
    $is_header_saved = 1;

    # save tweets
    OUTER:
    while(1){
      my($id, $tweet);
      while(<TWHTML>){
        last OUTER if m#</ol>#;
        $id = $1 if /id="status_(\d+)"/;
        s#(<span class="published timestamp" data="{time:'([^']+)'}">)[^<]+</span>#$1 . repTime($2) . "</span>"#e;
        $tweet .= $_;
        last if m#</li>#;
      }

      # add $url_screen_name and $thumb_span if not contained.
      if( $tweet !~ m#class="tweet-url screen-name"#){
        $tweet =~ s#<span class="status-body">#$thumb_span <span class="status-body" style="padding-left:48px;">#;
        $tweet =~ s#<span class="status-content">#<span class="status-content"> $url_screen_name#;
      }

      $TWEET{$id} = $tweet;
    }

    if(not $is_footer_saved){
        @FOOTER = $_;
        # save remaining
        while(<TWHTML>){
          push @FOOTER, $_;
        }
        $is_footer_saved = 1;
    }

    close TWHTML;
}

print @HEADER;
for (sort keys %TWEET){
    print $TWEET{$_};
}
print @FOOTER;

☆軽量で操作性の良いTwitterクライアントPino

少し前に見つけたPinoというTwitterクライアントの紹介です。
TwitterのほかにIdenti.ca というのにも対応しているようです。
Valaとう言語で書かれています。
Version 0.21では日本語が入力できなかったのですが、0.24になって日本語入力できるようになりました。
残念ならが日本語化されていません。
本家サイトはこちら
pino-twitter - Project Hosting on Google Code

導入方法は本家サイトにも書いてある通りで、Karmic以上であれば下記のコマンドで導入できます。
Lucid Beta-1でも同じ操作で導入できることを確認しています。

$ sudo add-apt-repository ppa:vala-team/ppa 
$ sudo add-apt-repository ppa:troorl/pino
$ sudo apt-get update
$ sudo apt-get install pino

画面はLucidに入っているGwibberと似ています。検索機能や複数のTLを流す機能はありません。
気にいっている点は、
・軽快な動作
・Ctrl+1,Ctrl+2,Ctrl+3,Ctrl+4でHome、Mentions、Direct Message、User Infoを切替可能
・投稿はAlt+Enter
最後の投稿操作は何処にも書いていなかったので迷いました。
しかし私は漢字変換の確定にEnterを使うので、誤ってPostしてしまうことが少なくてよいと思っています。


メイン画面のスクリーンショット

User InfoはHome TimelineやMentionsでプロフィール画像をダブルクリックして表示する方法と、
ユーザ名を入力する表示する方法の2種類があります。
残念ながらダイレクトメッセージを頂いたことがないのでDirect Messagesはさびしいスクリーンショットになってしまいました。

Home Timeline (Ctrl+1) Mentions (Ctrl+2) Direct Messages (Ctrl+3) User Info (Ctrl+4) [Show User]押下後のUser Info

メニューの中身

これは見たまんまです。特に説明は不要だと思います。
複数のアカウントを登録して切り替えることが出来そうですが、一つしかアカウントを持っていないのでわかりません。

[Pino] [Edit] [View] [Help]

Preferences(設定画面)

デフォルトではフォントサイズが9ポイントなのですが、日本語は9ポイントではちょっときついと思うので大きくすることをお勧めします。
[Enable Spell checking]はオフの方がいいと思います。(これを書いていて気付きました。)

Main Desktop Account Appearance

gnome-sudoku bug evidence

initial state
Symbol definition
A:Cell(1,5)
B:Cell(1,7)
C:Cell(2,9)

Case1 and Case6 are wrong.
Last input Number dose not change to red.

Case1
A->B->C
Case2
A->C->B
Case3
B->A->C
Case4
B->C->A
Case5
C->A->B
Case6
C->B->A

☆KarmicにBlogtk2をいれるとGwibberが起動しなくなる件

KarmicでjayredingさんのPPAを追加してblogtkを導入すると、gwibberが起動しなくなることに気付きました。
blogtk2の起動でも出た"enchant error for language: ja_JP.UTF-8"です。
原因はgwui.pyとblogtk(2)につられて入るpython-gtkspellです。
(/usr/lib/python2.6/dist-packages/gwibber/gwui.py)
問題になるのは最初のほうにある

try:
  import gtkspell
except:
  gtkspell = None
と、後ろのほうにある
    if gtkspell:
      self.spell = gtkspell.Spell(self, None)

gwibberパッケージの依存性からはpython-gtkspellパッケージが導入されません。
したがって冒頭部分でgtkspellがNoneになるので、後ろのほうのコードが実行されません。

ここへpython-gtkspellパッケージが導入されると後ろのほうのコードでエラーが発生します。

もともとgwibberはpython-gtkspellなしで動作しますし、
gwibberがらみのソースをgrep -i -e spell しても使っている形跡がありません。
下記のように変更してしまえばgwibberが動作するようになります。

gwui.pyへのパッチ
@@ -15,10 +15,11 @@
 
 IMG_CACHE_DIR = os.path.join(resources.CACHE_BASE_DIR, "gwibber", "images")
 
-try:
-  import gtkspell
-except:
-  gtkspell = None
+# try:
+#   import gtkspell
+# except:
+#   gtkspell = None
+gtkspell = None
 
 class Color:
   def __init__(self, hex):

ついでにblogtk(2)の方も見てみたのですが、こちらもgtkspell.Spellから取得したものを使っている様子はありません。
なんなのでしょうかgtkspell。なぞです。

この時期なのでLucid Beta1でどうなったか気になったので調べてみました。
blogtkのバージョンは古い系統の1.1-2でpython-gtkspellとは無縁でした。
(依存性の指定が不足しているようで起動エラーになりましたが....)
gwibberは新しくなっていて、依存パッケージにpython-gtkspellが入っています。
さすがに標準アプリになっただけあって起動エラーにはなりません。
調べてみるとgtkspell.Spellの呼び出しがtry:、except: で囲まれてました。
grep -i -e spellしてみたのですが、相変わらず使われてなさそうです....。
なんなのでしょうかgtkspell。なぞです。

☆普段使わないパッケージコマンドやオプションをメモ

普段使わないパッケージ関連のコマンドやオプションをとりあえずメモ
・apt-file
  ファイルの検索
  --ignore-case(-i)
  --regexp(-x)

・apt-cache showsrc

・gep-dctrl,grep-status,grep-available
  sea also "/var/lib/dpkg/status","/var/lib/dpkg/available"

・apt-cache dumpavail
  利用可能なパッケージの表示

・apt-cache showpkg
  パッケージの情報をAPTの内部形式で表示
  sea also apt-show-versions


・apt-get --simulate
  実際には実行しない installと一緒に使う

・apt-get --print-uris
  debファイルの取得元を表示する。installと一緒に使う
  キャッシュにファイルが存在すると表示しない
  この場合はcleanするか--reinstallを指定。

・apt-listchanges
  パッケージの変更履歴を確認する
  同名のパッケージを導入する必要あり
  導入後 sudo dpkg-reconfigure apt-listchanges
  http://ascii.jp/elem/000/000/475/475007/index-3.html
  http://www.debian.org/doc/manuals/apt-howto/ch-search.ja.html

・apt-rdepends
  依存関係の表示。再帰する。
  --reverse,--build-depends とかある
  sea also apt-cache depends,apt-cache rdepends
  http://d.ma-aya.to/?date=20091216

・apt-mark showauto
  依存関係で自動的にインストールされたパッケージを表示
  
・dpkg --audit
  パッケージの状態チェック

・dpkg --configure --pending
  インストールがこけた場合の修復

・dpkg-reconfigure
  設定のやり直し

・dpkg-query
・dpkg --stasus(-s) パッケージ名
・dpkg --info(-i) パッケージファイル
・dpkg --field(-f) パッケージファイル
・dpkg --contents(-c) パッケージファイル

・dpkg-depcheck
  http://www.netfort.gr.jp/~ohura/diary/200501-1.html#07
  dpkg-depcheck(1) は特定のコマンドを strace 上で実行し、 そのコマンドが必要とするパッケージを表示する。 


・dpkg-source -x hoge.desc
  ソースを展開してパッチを適用
  (see also apt-get source)

・debfoster
  同名パッケージを導入する必要あり。

・apt-cache showsrc パッケージ名

・dpkg-buildpackage
・debuild
・dpkg-genbuilddeps
・debsign

・pkg-config
  gcc `pkg-config --cflags --libs` みたいに使う
  http://dolphin2005.blog.so-net.ne.jp/2008-02-17-2

・ディレクトリ、ファイル
/var/lib/dpkg/info

/var/log/installer/initial-status.gz
  インストール時のstatus

・推奨パッケージをインストールしない
sudo apt-get --no-install-recommends install devscripts

・パッチを当てたあとにパッケージのバージョンを上げる
dch -v "VERSION"

changelogの雛形が作成されるので、適当に入力してCtrl+Oで保存
このときファイル名から「.dch」を取り除く

☆Twitterの投稿時刻をPerlでローカル時刻にする

Twitterの投稿時刻は投稿してから24時間以内は"約○○前"のように現在との時間差で表示されます。
ログインしていない場合、1日以上経過しているつぶやき時刻は日本との時差がなぜか17時間(PSTの場合。PDTの時期は16時間)もあります。
"約○○前"の表示はあまり好きではありませんし、自分のつぶやき時刻が17時間もずれて表示されているとびっくりしてしまいます。
そこでPerlで時刻の表示を一律 %Y/%m/%d %H:%M:%S の形式に変更しようと思いました。
使用する環境はUbuntu 9.10 Karmic koalaです。

目をつけたのは、下記の data="{time: の後ろの文字列です。

<span class="published timestamp" data="{time:'Tue Mar 16 12:44:41 +0000 2010'}">約1時間前</span>
この形式を扱えるモジュールを探します。
"perl Datetime"で検索すると、結構たくさんのモジュールが引っかかるのですが、どれを使えばよいのかわかりません。
とりあえず、DateTime::Format::W3CDTF と DateTime::Format::Mail を試したのですが、うまくいきません。
検索条件をいろいろ変えてみて、Twitter のStreaming APIにおける日時表現の解析についてというサイトに行き着きました。
DateTime::Format::DateParse で可能と書いてありました。私が扱おうとしているのはStreaming APIではないのですが、時刻の形式は一緒です。

DateTime::Format::DateParseは "libdatetime-format-dateparse-perl" パッケージに含まれています。
依存関係は

Depends: perl (>= 5.6.0-16), libdatetime-timezone-perl (>= 0.27), libdatetime-perl (>= 0.29), libtimedate-perl (>= 1.16)
となっています。

Manpageをみると

SYNOPSIS
           use DateTime::Format::DateParse;

           my $dt = DateTime::Format::DateParse->parse_datetime( $date );
           my $dt = DateTime::Format::DateParse->parse_datetime( $date, $zone );

となっているのですが、なぜか引数を2つとる形式はうまく動きませんでした。

DateTime::Format::DateParseを先日のtw_rev.plに組み込むと下記のようになります。

tw_rev2.pl
#!/usr/bin/perl
use DateTime;
use DateTime::Format::DateParse;
my $tzhere = DateTime::TimeZone->new( name => 'local' );

sub repTime
{
  my $gmt = shift;
  $dt = DateTime::Format::DateParse->parse_datetime($gmt);
  $dt->set_time_zone($tzhere);
  return $dt->strftime('%Y/%m/%d %H:%M:%S');
}


# search start html header
while(<>){
  print;
  last if m#<head>#;
}

# For relative link. ex list in right side.
print qq(<base href="http://twitter.com/" />\n);

# search start of tweets
while(<>){
  print;
  last if m#<ol id='timeline' class='statuses'>#;
}

# stack each tweets
my(@TWEET);
OUTER:
while(1){
  my(@tweet);
  while(<>){
    last OUTER if m#</ol>#;
    s#(<span class="published timestamp" data="{time:'([^']+)'}">)[^<]+</span>#$1 . repTime($2) . "</span>"#e;
    push @tweet, $_;
    last if m#</li>#;
  }
  unshift @TWEET, \@tweet;
}

# output each tweets
my($tweet);
for $tweet (@TWEET){
  my($line);
  for $line (@$tweet){
    print $line;
  }
}
print qq(</ol>\n);

# output remaining
while(<>){
  print;
}

☆TwitterのHTMLをPerlでDOM的に扱おうとして絶望

これは失敗の記録です。PerlでTwitterのHTMLをDOM的に扱おうとして断念しました。

●やりたかったこと

Twitterのタイムラインを通常の逆順(投稿時刻の昇順)に表示する。
先日作ったperlスクリプトは改行に依存している部分があってTwitterのHTMLが少し変わっただけで影響を受けてしまいます。
この欠点をXHTMLを解析できるモジュールを使って克服したい。

●そもそもなぜTwitterのAPIではなくHTMLを扱おうとしたか

GUIアプリを作るのは大変手間がかかる(LinuxではそもそもGUIアプリを作るスキルがない)ので、ブラウザで表示したい。
APIでとってきたXMLまたはJSONをHTMLに組み立てなおすのは結構手間がかかります。

●何がうまくいかなかったか

Twitterの返すHTMLは & がエンティティ化されておらず、XML::DOM::Parser、XML::LibXMLともにエラーが発生しました。
& をエンティティ化しようとすれば、それがリンクの一部でないかなどの判定が必要で、実質的にHTMLを解析する必要があります。
これではモジュールを使って手間を省く意味がありません。
また蛇足ですが、XML::DOMを使う場合、XML::DOM::DocumentはgetElemetByIDメソッドが存在しないので、
つぶやきの&lt;li&gt;を持っている&lt;ol&gt;ノード"&lt;ol id='timeline' class='statuses'&gt;"を取得するために余計なロジックが必要になります。
XML::LibXMLは&に引っかからない場合、idの重複でエラーが出ます。これはbody要素とひとつのdiv要素に同じid="profile"が存在するためです。
(XML::DOMがエラーを吐かないのが不思議です。)

参考:XML::DOM::Parserのエラーメッセージ(&が原因)
not well-formed (invalid token) at line 577, column 181, byte 27722 at /usr/lib/perl5/XML/Parser.pm line 187

参考:XML::LibXMLのエラーメッセージ(&が原因)
test.html:681: parser error : EntityRef: expecting ';'
参考:XML::LibXMLのエラーメッセージ(id重複が原因)
test.html:933: validity error : ID profile already defined

●その他、雑記

■Perlのモジュールに対応するUbuntuのdebパッケージを探す方法について

apt-cache searchはあてにならない気がします。
http://packages.ubuntu.com/ の"Search the contents of packages"は検索文字列に"/"が入っているとまったくマッチしなくなるようなので使えない。
apt-fileを使うのがベストだと思います。導入と使い方は Ubuntu Weekly Recipe 第16回参照。
XML::DOM::Parserを探したい場合、apt-file XML/DOM/Parser とやればOKです。

■debパッケージのないPerlモジュールはどうする

自分では試していませんが、Ubuntu日本語フォーラムの記事を見てみたらdh-make-perlでdeb化するのがよさげです。

■debパッケージがなかったので試さなかったPerlモジュール
▼HTML::TagParser

http://www.kawa.net/works/perl/html/tagparser.html
これはパースできるだけの模様。ノードを付け加えたり、削ってからHTMLを吐くというのは出来なさそうです。

▼XML::Liberal

http://search.cpan.org/dist/XML-Liberal/lib/XML/Liberal.pm
XML::Liberal - Super liberal XML parser that parses broken XML とあるので多少XMLに反する部分があってもやってくれそうです。
試してないのでわかりません。

●結局

☆Twitterのタイムラインを逆順に表示するで作成した、tw_rev.plは、つぶやきが含まれているol要素の始まりと終わり、各li要素の区切さえうまく捕まえることが出来ればよいように出来ています。
であれば、多少Twitterの吐くHTMLが変わったとしてもそれを補正してやるフィルタをかませてからtw_rev.plに入力すればいいので、大抵の場合、比較的簡単に対応が出来そうです。
直近にあった</li>のあとに<li>や</ol>が来てしまう変更は、tw_rev.plを直そうとすると以外に面倒ですが、sedで事前に改行を入れてやるだけで済みました。

☆Twitter のタイムラインを逆順に表示する(3)

こんどはpython-twitter編です。
Ubuntu(Karmic使ってます)ならapt-getで入ります。
ドキュメントをみてもソースコードをgrepしてみてもリスト機能に対応していないようです。

ならば、継承して足せばよい、というわけでリストのタイムラインを取ってくる機能だけ追加してみました。
テストのために取ってきた情報を端末へ出力するコードも書いてあります。
xfterm4やgnome-terminalであればURLを右クリックしてブラウザで開けるので
これだけで簡易Twitterリーダーになります。
(取ってきた情報でHTMLを組み立てるのが面倒なだけです....)

これをやって気づいたのはアイコンの重要性。
誰が発言したかを見分けるのにとても役に立ってるんだと再認識しました。

引数は二つ、ユーザと取得数です。リストのタイムラインを取得したい場合は user/listのようにして指定します。

tw_list.py ubuntubot 50
tw_list.py MidSpecLowLoad/ubuntu-ittoke-doujou 50
出力の例
--------------------------------------------------------------------------------
ubuntubot : 2010-03-03 10:22:49 
Ubuntuのシステムディスクを交換 - こだわり、ときどき、やけくそ ...: UbuntuのCD-ROMから起動する。ここでは、日本語Remixのデスクトップ版を使う。 dumpと restoreをインストールする. /et... http://bit.ly/8YpApo
--------------------------------------------------------------------------------
ubuntubot : 2010-03-03 10:22:50 
Tue, Mar 02 on Twitter - doronkoのTwitterログ: Ubuntu 10.04はJailbreakなしでiPhoneにフルアクセス可能!? - ユーザー報告が話題 http://bit.ly/c... http://bit.ly/9E1fTD
--------------------------------------------------------------------------------
ubuntubot : 2010-03-03 12:41:55 
【レポート】 インストールしてから5分で動かせる - CUDAをサポートしたLinuxが登場: マイコミジャーナル
FedoraやRHEL、Ubuntuなどと比べ、システム開発などにおいて不要なプロセスやサービスを停止させること... http://bit.ly/cPOHG6
--------------------------------------------------------------------------------
ubuntubot : 2010-03-03 14:35:21 
「Ubuntu 10.04」アルファ第3版で大発見--iPhoneやiPod touchに対応するとのユーザー報告: 現在アルファ第3版の状態にあるUbuntu 10.04(開発コードLucid Lynx)がiPhoneやiPo... http://bit.ly/dvFHJk
--------------------------------------------------------------------------------
ubuntubot : 2010-03-03 16:36:56 
仮想HD(VHD)を使うための準備 ≪ むずかしいことはわかりません: ステップ実行してるとこのまま実行してやりたい衝動にかられるけど、ここで我慢しないと今までの時間が無駄になる 19 hours ago; ものは試しと ubu... http://bit.ly/b5Bmgx

2010/03/07 引数チェック追加
tw_list.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import codecs
import simplejson
import twitter
import time

class ApiSupportsList(twitter.Api):
  def GetUserListTimeline(self, user=None,ulist=None, count=None, since=None, since_id=None):
    try:
      if count:
        int(count)
    except:
      raise twitter.TwitterError("Count must be an integer")
    parameters = {}
    if count:
      parameters['per_page'] = count
    if since:
      parameters['since'] = since
    if since_id:
      parameters['since_id'] = since_id
    if not user and not self._username:
      raise twitter.TwitterError("User must be specified if API is not authenticated.")
    else:
      user = user if user else self._username
      url = 'http://api.twitter.com/1/%s/lists/%s/statuses.json' % (user, ulist)   
    json = self._FetchUrl(url, parameters=parameters)
    data = simplejson.loads(json)
    self._CheckForTwitterError(data)
    return [twitter.Status.NewFromJsonDict(x) for x in data]

sys.stdout = codecs.getwriter('utf_8')(sys.stdout)
api = ApiSupportsList()

argv = sys.argv
argv.pop(0)
user, count = argv

if user.count('/') == 1:
  user, group = user.split('/')
  statuses = api.GetUserListTimeline(user,group,count)
else:
  statuses = api.GetUserTimeline(user,count) 

statuses.reverse()
for status in statuses:
  print '-' * 80
#  print '%s : %s (%s)' % (status.user.screen_name, time.strftime('%F %T',time.localtime(status.created_at_in_seconds)), status.relative_created_at)
  print '%s : %s ' % (status.user.screen_name, time.strftime('%F %T',time.localtime(status.created_at_in_seconds)))
  print status.text
  

☆Twitterのタイムラインを逆順に表示する(2)

昨日の続きです。
TwitterのAPIを参照するとURLの後ろにパラメータが使えるようです。
http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-GET-list-statuses
http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses-user_timeline
昨日のスクリプトのに与える引数に取得したい数する場合は "?per_page=50"
(50は取得したい数、1~200が指定可能)を追加します。シェルが?を展開しようとするのでクオートします。
per_pageはリストのAPIにのっていて、指定ユーザのタイムラインを取得すところにはなかったのですが、
両方動作します。

実行例
tw_rev_browse.sh 'ubuntubot?per_page=50'
tw_rev_browse.sh 'MidSpecLowLoad/ubuntu-ittoke-doujou?per_page=50'

これだけでは何なので、シェルをちょっといじって同一ファイルに一定間隔で取得内容を
書き出して、ブラウザはF5キーを押してリロードする仕組みにしてみます。
(HTMLのヘッダにJavascriptでリロードを仕込もうとしたのですが、うまくやれませんでした。)
終了されるときはCTRL+Cで止めます。
下のスクリプトでは3分にしています。変更したい場合は "GET_INTERVAL"の値をいじってください。

実行例
tw_rev_browse_cont.sh 'MidSpecLowLoad/ubuntu-ittoke-doujou?per_page=50'
2010/03/07 引数チェック追加
tw_rev_browse_cont.sh
#!/bin/bash

GET_INTERVAL=3m

if [ -z $1 ] ;then
  echo "$0 requires a parameter"
  exit 1
fi

HTML=TwitRev-`date '+%F-%H%M%S'`-$$.html

wget -O - http://twitter.com/$1 |sed -e 's#</li>#\n#;'| tw_rev.pl > $HTML
x-www-browser $HTML &

while true;do
  sleep $GET_INTERVAL
  echo "--- Start update $HTML:" `date '+%F %T'` " ---"
  wget -O - http://twitter.com/$1 |sed -e 's#</li>#\n#;'| tw_rev.pl > $HTML
  echo "--- End   update $HTML:" `date '+%F %T'` " ---"
done

APIによれば取得し始めのIDなんかを指定できるようです。
サーバーにかける負荷や自分のマシンリソースを無駄遣いしないように、
新規のつぶやきだけとってくるようにするのが望ましいのですが、
今日はとりあえずここまで。

☆Twitterのタイムラインを逆順に表示する

Twitterのタイムラインを逆順に表示したいのですが、Read All Tweetsは一つのリストしか指定できないみたいです。
複数のリストを見るために設定を変えるのは結構面倒です。
とりあえず手持ちの知識だけでやれることをやってみました。
この方法では一ページ分しか処理できません。

tw_rev_browse.shにブラウザで見た場合のURLから"http://twitter.com/"を除いた部分を指定して使います。
account_nameのアカウントのタイムラインを見たい場合は "tw_rev_brose.sh account_name"、
リストの場合は"tw_rev_brose.sh account_name/list_name"のようにして使います。
結果はブラウザで表示されます。
TwitRev-YYYY-MM-DD-HHMMSS-数字何桁か.html というファイルがカレントディレクトリに出来るので不要になったら削除してください。

実行例
# アカウント指定
$ tw_rev_browse.sh ubn
# リストの指定
$ tw_rev_browse.sh MidSpecLowLoad/ubuntu-ittoke-doujou

以下の二つのファイルに実行権限をつけてをパスの通ったところにおきます。

2010/03/07 引数チェック追加
tw_rev_browse.sh
#!/bin/bash
if [ -z $1 ] ;then
  echo "$0 requires a parameter"
  exit 1
fi

HTML=TwitRev-`date '+%F-%H%M%S'`-$$.html
wget -O - http://twitter.com/$1 |sed -e 's#</li>#\n#;'| tw_rev.pl > $HTML
x-www-browser $HTML &
tw_rev.pl
#!/usr/bin/perl

# search start html header
while(<>){
  print;
  last if m#<head>#;
}

# For relative link. ex list in right side.
print qq(<base href="http://twitter.com/" />\n);

# search start of tweets
while(<>){
  print;
  last if m#<ol id='timeline' class='statuses'>#;
}

# stack each tweets
my(@TWEET);
OUTER:
while(1){
  my(@tweet);
  while(<>){
    last OUTER if m#</ol>#;
    push @tweet, $_;
    last if m#</li>#;
  }
  unshift @TWEET, \@tweet;
}

# output each tweets
my($tweet);
for $tweet (@TWEET){
  my($line);
  for $line (@$tweet){
    print $line;
  }
}
print qq(</ol>\n);

# output remaining
while(<>){
  print;
}

HTMLを見ると1Tweetが<li></li>でまとめられているのでJavaスクリプトでDOMを操作すれば、結構簡単な気がします。
Firefoxのプラグインの作り方を知っていればこれが一番簡単そうです。
Perlを使う場合でもDOMを操作できるモジュールを使えばもっとスマートに出来るとは思うのですが、モジュールはあまり使用したことがないので知りません。
python twetterなどを調べて使ってみるのも良いかもしれません。
何か一つくらいチャレンジしてみるつもりです。

☆Startup-Managerに頼らないUsplashの変更

☆Karmic のusplashをカスタマイズではUsplashを切り替えるためにStartup-Managerを使用しました。
しかし、Startup-Mangerは起動しただけで update-grub が走るようでやたらディスクアクセスが発生しますし/boot/grub/grub.cfgのタイムスタンプが更新されていたりします。
そこで、"update-alternatives"でなんとかしようと思い、検索しました。
とてもわかりやすいサイトが出てきたので、これを参考にして作業してみます。

参考サイト: コマンドを便利に切り替える update-alternatives 使いこなし講座

2010/03/03 注追記:☆Karmic のusplashをカスタマイズで行った /etc/default/grub の変更を反映するためには下記の手順だけでなくupdate-grubの実行が必要です。

●現在の状態の確認

update-alternatives --display を使います。

$ update-alternatives --display usplash-artwork.so
usplash-artwork.so - auto mode
 リンクは現在 /usr/lib/usplash/usplash-theme-xubuntu.so を指しています
/usr/lib/usplash/usplash-theme-ubuntu-mono.so - 優先度 20
/usr/lib/usplash/usplash-theme-ubuntu.so - 優先度 10
/usr/lib/usplash/usplash-theme-xubuntu.so - 優先度 55
現在の `最適' バージョンは /usr/lib/usplash/usplash-theme-xubuntu.so です。


このとき設定ファイルがどうなっているか確認します。

$ cat /var/lib/dpkg/alternatives/usplash-artwork.so 
auto
/usr/lib/usplash/usplash-artwork.so

/usr/lib/usplash/usplash-theme-ubuntu-mono.so
20
/usr/lib/usplash/usplash-theme-ubuntu.so
10
/usr/lib/usplash/usplash-theme-xubuntu.so
55

●設定の変更

変更方法には--setオプションを使う方法と--configオプションを使う方法の2つがあります。

■設定の変更(--set を使う場合)

直接ファイル名を指定して変更を行います。
パッケージのpostinstの"update-initramfs -u"の前にこれを使いしておけばよかったんですね。
そうしないと"update-initramfs -u"は無駄撃ちです。

$ sudo update-alternatives --set usplash-artwork.so /usr/lib/usplash/usplash-theme-ubuntu-mono.so
update-alternatives: using /usr/lib/usplash/usplash-theme-ubuntu-mono.so to provide /usr/lib/usplash/usplash-artwork.so (usplash-artwork.so) in manual mode.


このとき設定ファイルがどうなっているか確認します。
先頭行がmanualになるだけでどれが選択されているかは記録されないようです。

$ cat /var/lib/dpkg/alternatives/usplash-artwork.so 
manual
/usr/lib/usplash/usplash-artwork.so

/usr/lib/usplash/usplash-theme-ubuntu-mono.so
20
/usr/lib/usplash/usplash-theme-ubuntu.so
10
/usr/lib/usplash/usplash-theme-xubuntu.so
55

■設定の変更(--config を使う場合)

--configと機能名を指定して、出てきたプロンプトに選択する番号を指定します。
(1 を指定して/usr/lib/usplash/usplash-theme-ubuntu-mono.soを選択)

$ sudo update-alternatives --config usplash-artwork.so
There are 3 choices for the alternative usplash-artwork.so (providing /usr/lib/usplash/usplash-artwork.so).

  Selection    Path                                           Priority   Status
------------------------------------------------------------
* 0            /usr/lib/usplash/usplash-theme-xubuntu.so       55        auto mode
  1            /usr/lib/usplash/usplash-theme-ubuntu-mono.so   20        manual mode
  2            /usr/lib/usplash/usplash-theme-ubuntu.so        10        manual mode
  3            /usr/lib/usplash/usplash-theme-xubuntu.so       55        manual mode

Press enter to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/lib/usplash/usplash-theme-ubuntu-mono.so to provide /usr/lib/usplash/usplash-artwork.so (usplash-artwork.so) in manual mode.

●initrdの更新

update-initramfsでinitrdを更新します。

$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-2.6.31-19-generic

これで再起動のときからUsplashが変更されます。

●おまけ。設定の変更(ついでに--autoで推奨設定に戻す)

このあとupdate-initramfs -u も必要です。

$ sudo update-alternatives --auto usplash-artwork.so
update-alternatives: using /usr/lib/usplash/usplash-theme-xubuntu.so to provide /usr/lib/usplash/usplash-artwork.so (usplash-artwork.so) in auto mode.

☆Wine上に手作業で導入したプログラムをメニューに表示させる

昨日Wine上に導入したMZ4はインストーラーがなかったのでメニューに表示されません。
何処かにズバリの手順がないか検索したのですが、見つけられませんでした。
そこで私が手作業した方法を記述して公開します。
参考にしたサイトは前回も引用した2サイトです。
A. http://linuxos.blog102.fc2.com/blog-entry-125.html
B. http://qubuntu.blogspot.com/2009/10/wine.html

●必要なパッケージの導入

icoutils と imagemagick を導入します。

$ sudo apt-get install icoutils imagemagick

●アイコンの準備

実行ファイルから.icoファイルを抽出し、pngに変換します。
wrestoolのオプション-t14 はリソースタイプが14(アイコン)の指定です。-xは抽出の指定。
カレントディレクトリにicoファイルがたくさん出来るので画像ビューアで確認します。
使用するものを決めたらpngに変換して ~/.local/share/icons/ に出力します。

$ cd ~/.wine/drive_c/bin/MZ4
$ wrestool -x --output=. -t14 mz4.exe
# どのファイルを使うか画像ビューアで確認 mz4.exe_14_139_1041.ico を使うことにします。
$ convert mz4.exe_14_139_1041.ico ~/.local/share/icons/MZ4.png
$ rm *.ico

●desktopファイルの作成

~/.local/share/applications/wine/Programs/ ディレクトリにMZ4.desktopを作成します。
内容は下記の通り
hogeはユーザの名称に置き換えてください。
MZ4を展開したディレクトリが違う場合は適宜置き換えてください。
Nameに指定した文字列がメニューに表示されます。
Categoriesはメニューの何処に表示されるかの指定です。[ネットワーク]に表示されるようにしました。

~/.local/share/applications/wine/Programs/MZ4.desktop
[Desktop Entry]
Name=MZ4 1.31
Exec=env WINEPREFIX="/home/hoge/.wine" wine "C:\\bin\\MZ4\\mz4.exe"
Type=Application
StartupNotify=true
Path=/home/hoge/.wine/dosdevices/c:/bin/MZ4
Icon=/home/hoge/.local/share/icons/MZ4.png
Categories=Network;

●menuファイルの作成

~/.config/menus/applications-merged/に下記の内容でファイルを作ります。

~/.config/menus/applications-merged/wine-Programs-MZ4.menu
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN"
"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
<Menu>
  <Name>Applications</Name>
  <Menu>
    <Name>wine-wine</Name>
    <Directory>wine-wine.directory</Directory>
  <Menu>
    <Name>wine-Programs</Name>
    <Directory>wine-Programs.directory</Directory>
    <Include>
      <Filename>wine-Programs-MZ4.desktop</Filename>
    </Include>
  </Menu>
  </Menu>
</Menu>

●ログアウトとログイン

上記の手続きを行うと、なぜかメニューの[その他]が表示されなくなりました。
ネットワークのところにもまだ追加したものが出てきません。
一旦ログアウトしてログインすると出てきました。
これは何かコマンドで対応できるのかもしれませんが見つけられませんでした。

●その他

~/.local/share/applications/ の直下には拡張子の関連付け情報が格納してあるようです。
これについてはまたの機会に。

☆Wine上で動かしているその他のアプリケーション

Wineで使っているその他のアプリ。

2010/02/27 訂正 Peggy PadとPeggyは日本語入力がダメでした。

●nPOPQ

フリーのPOP3メーラーです。
複数のPCでメールを見るので、通常使うメーラーは受信時にメールを削除しない設定にしています。
そこでスパム削除用にnPOPQを使ってます。
Windowsで設定をした状態でディレクトリごとWineへ持っていったものがそのまま動きます。
今のところおかしな挙動はありません。非常に軽快に動作します。オススメです。

●Peggy Pad

フリーのテキストエディタです。
Ubuntuでは主にnPOPQのメールビューアとして使っています。
インストーラで導入してフォントだけ設定してます。
特におかしな挙動はありません。URLを選択してCTRL+Bでブラウザが立ち上がります。
Perlと同じような拡張正規表現を使用した検索や置換えがつかえます。
Geanyを使うようになってあまり使っていませんが、文字コード周りはGeanyより強い気がします。

●Peggy Pro for PHP

シェアウェアのテキストエディタです。
残念ながら動作しない機能が結構あります。
とくに外部ツールをつかう機能はだめのようです。残念です。非常に残念です。
ほかには全角・半角変換などが動作しません。
これが完全に動作すればDRM付動画を見る以外はUbuntuでやっていけるのに。

☆TwitterのListに対応したMZ4をWineで使う

Twitterのリスト機能に対応したクライアントソフトを探してたのですがTweetDeckくらいしか見つけられませんでした。
そこでWindowsに探索範囲を広げるとMZ4が対応してました。以前はよく使っていたのに気づきませんでした。
最新バージョンは1.31、使っていたのは1.018でした。(1.018はリスト未対応)
残念ながらWineで使うとWebブラウザとの連携がうまくいきません.....。 一応導入方法を。
2010/02/27 追記 日本語入力がダメでした。

●導入

http://www.mz3.jp/mz3.html の下のほうから MZ4.1.3.1.lzh をダウンロードします。
ZIPなのでwineでインストールできません。適当なディレクトリを作って解凍します。
私は ~/.wine/drive_c/bin/MZ4 というディレクトリを作って解凍しました。

cd ~/.wine/drive_c/bin/MZ4
lha -x ~/Downloads/MZ4.1.3.1.lzh

メニューに出るようにしたい場合は
http://linuxos.blog102.fc2.com/blog-entry-125.html
http://qubuntu.blogspot.com/2009/10/wine.html
を参照してチャレンジしてください。

2010/02/28 この件に関するフォローを投稿しました。☆Wine上に手作業で導入したプログラムをメニューに表示させる

私はコマンドライン起動で十分なので下記のようなシェルスクリプトを~/bin/mz4として作成し、実行権限をつけます。

#!/bin/bash
env WINEPREFIX="/home/masaya/.wine" wine "C:\\bin\\MZ4\\mz4.exe" 

●設定

初回起動時に[クライアントタイプの選択]というダイアログが出ます。
TwitterとRSSは動作しますが、gmailとGoogleReaderはだめのようです。他のサービスは試していません。
mixiは使っていないのであればチェックをはずしておいたほうがいいです。(たくさんタブが作られてしまいます。)
次にログイン設定をします。ドロップダウンリストからTwitterを選択してIDとパスワードを入力します。
ログイン設定はあとでメニューの[設定]-[ログイン設定]で変更できます。
この後、絵文字ダウンロードに関するダイアログが出ますが、Twitterには関係なさそうなので2回とも[いいえ]を選びます。

1.クライアントタイプの選択 2.2番目のダイアログ 3.ログイン設定 4.4番目(絵文字) 5.5番目(絵文字)

メイン画面がすでに立ち上がっていますが、文字が出力されている部分は背景が描画されていません。
うっとうしいのでmz4を解凍したディレクトリのskin/aluminium/*.jpgを別へ移動してしまいます。
これで再起動すると背景が薄い灰色になります。
あとはメニューの[設定]-[一般]から起動するダイアログを覗いて、好みに動作をかえてください。

メイン画面
文字の背景は白
メイン画面
背景画像削除後

画面上部の[リスト一覧]をクリックすると自分で作ったリストの一覧が下部に表示されます。
下部に表示されたリストの部分をクリックするとそのリストのタイムラインが表示されます。

画面上部の[リスト一覧]をクリック
画面下部のリストをクリック

タイムラインを右クリックするとコンテキストメニューが出てきます。
冒頭に書いたように残念ながらブラウザでリンクを開けません。
ターミナルから起動している場合、下記のようなエラーメッセージがでます。

err:winebrowser:get_url_from_dde Unable to retrieve URL from string L"\""
err:winebrowser:wmain Usage: winebrowser URL
検索してみると既知の問題で特にMZ4のせいではないようです。
http://bugs.winehq.org/show_bug.cgi?id=13891

☆UbuntuにSylpheed 3.0を導入

今日、Sylpheedを立ち上げたら更新通知のダイアログ。初めて見ました。
ホームページを見るとマルチスレッドに対応してユーザの操作が不能になる場面が少なくなりましたと書いてあります。
それほど操作不能が気になったことはないのですが、入れてしまいます。
(メインはWinXP環境なのでメールさえ消してくれなければOKなのです。)

バージョンアップだから update-manager では更新できません。PPAを探します。
googleでppa sylpeedで検索。ヒットあり。
AWASHIRO Ikuyaさんのは3.0のベータでHajime Mizunoさんのは2.7.1。
Klaus Vormwegさんのが2010/02/24になっていて3.0正式版のようです。

sudo add-apt-repository ppa:klaus-vormweg/ppa
gksudo update-manager
とすると、お、geanyもちょっとだけ上がったのが出てますね。
使うアプリの趣味が合いますね。Klaus Vormwegさん。
sylpheed、sylpheed-i18n、geanyをアップデートしました。
立ち上げてみてもあまり変わりません。
設定ファイルのディレクトリの名前も.sylpheed-2.0ままです。
しばらく様子を見てから別のPCを更新するかどうか決めます。

☆モザイク加工のためにGimpは重すぎるので

今まではGimpでスクリーンショットにモザイク加工をしていたのですが、重くてどうもなじめません。
代替のソフトを探してみたのですが適当なものが見当たりませんでした。
そこでJTrimというWindows用のソフトをwine経由で使うことにしました。
(このためだけにwineを導入しているわけではなく、私はテキストエディタとメーラー(スパム削除用)、あまり使いませんがTwitterクライントをwine経由で入れてます。)
環境はUbuntu 0910 Karmic Koala、wine1.2です。

●導入

上記のJTrimのリンクからセットアップ版、jt153c.exe をダウンロードします。
wineをダウンロードしたファイルを引数にして立ち上げます。

wine ~/Downloads/jt153c.exe 
インストーラー画面は文字化けしていますが、かまわずにデフォルトで進めていきます。
(ショートカットの作成場所を選択するチェックボックスはオフにしました。)
インストールが終了すると[メニュー]/[その他]の下にJTrimが表示されるようになります。
起動すれば使えます。インストーラーと違って文字化けはありません。
ログインしてから初めてwineアプリを起動するときは若干重いのですが、それ以降は軽快に起動します。

●設定

JTrim本体はとくに設定をしていません。デフォルトで使っています。
ここではMirageと連携させるためにMirage側の設定をします。
ついでにショートカットキーでスクリーンショットを取る際にファイル名を気にせずにバシバシ [Alt]+Printを叩けるようにxfceの設定をいじります。

■xfceのショートカットキー設定

まずはxfceのショートカットキー設定。xfce4-keyboard-settings を起動して[アプリケーションショートカットキー(C)]タブを開きます。
ショートカットキーがPrintとなっている行の左側をダブルクリックして

scrot 'scrot-%F-%H%M%S.png'
と入力します。同じように<Alt>Printの左側を
scrot -b -u 'scrot-%F-%H%M%S.png'
と入力します。"%F-%H%M%S" の意味はdateコマンドに指定できるものと同じです。manpageを参照してください。
これで一秒以上間をあければ重複しないファイル名でスクリーンショットが取れます。間隔一秒未満で取得したい場合は%Nをつければいけそうです。
パスを指定していないので ホームディレクトリにファイルができます。パスを指定する際は 環境変数や~が使えないので注意してください。

ちなみにGnomeデスクトップでは"<Alt>Print"をどうしても置換えできませんでした。
設定画面で"<Alt>Print"を押しても"<Alt>Excute"になってしまうからです。
設定ファイルを開いて修正しても、もともとのショートカットが無効にならず手詰まりになりました。もともとのショートカットは設定画面でショートカットの列をクリックしてから[Backspace]で無効に出来ます、その後gconf-editorで設定を変更したのですが、それでもダメでした。
(xfce4-keyboard-settingsでも"<Alt>Print"を押すと"<Alt>Excute"なるのですが、上記のようにデフォルト設定のコマンド側だけを変えればよいので問題ありません。)

■Mirageの設定

Mirageのカスタムアクションを設定して、見ている画像をJTrimに引き渡して起動できるようにします。
Jtrimの起動コマンドラインは長いのでシェルを作っておきます。
~/.local/share/applications/wine/Programs/JTrim/JTrim.desktop の Excec=で始まる行に "$@" を追加したものを用意します。

~/bin/jtrim
#!/bin/bash
env WINEPREFIX="/home/masaya/.wine" wine "C:\\Program Files\\JTrim\\JTrim.exe"  "$@"

Mirageを立ち上げてメニューから[Edit]-[Custom Actions]-[Configure]を選択します。
右横の[+]ボタンを押してCustom Actionを追加します。
入力内容は下図参照。ショートカットは "<Control>r" を割り当てました。
メイン画面で "<Control>r" を押すと表示している画像をJTrimで開けるようになります。

Custom Actionの入力画面 Custom Actionの設定画面

●JTrimの操作(モザイク処理)

モザイクをかけたい場所をあらかじめドラッグして矩形選択しておきます。(選択範囲がないと画像全部に適用されます。)
メニューの[加工]-[ガウスぼかし]を選択すればOKです。意味なくキーボードの図の部分をぼかしてみました。