rxvt-unicode-9.31 でプロンプトの表示位置がおかしい件
目次
現象
調査記録
元のバージョンにダウングレードする
以前 FreeBSD 12.2-RELEASE で rxvt-unicode 9.30 を使用していたときは問題なかったので、9.31 から 9.30 にダウングレードしてみます。
※ この作業は ports ツリー全体をダウングレードすることになるので、ソフトの依存関係が破壊されるかもしれません。そうなってもいいように実験用の仮想環境を作り、その中で確認しました。
ports のダウングレードには gitup というツールを使います。カーネルのソースコードや ports を、リポジトリから clone したり pull したりすることに特化したツールです。設定ファイルが /usr/local/etc/gitup.conf にありますが、今回はデフォルトのまま使用します。GitHub のリポジトリ (https://github.com/freebsd/freebsd-ports) 等で rxvt-unicode 9.30 が含まれていそうなタグを探ると「release/13.1.0」というタグがよさそうだったので、これを指定して gitup を実行します。
# gitup ports -t release/13.1.0
rxvt-unicode のディレクトリに移動して make を実行します (コンパイルのみ。インストールはしない)。
# cd /usr/ports/x11/rxvt-unicode/
# make
# work/stage/usr/local/bin/urxvt -h
rxvt-unicode (urxvt) v9.30 - released: 2021-11-27
...
無事にコンパイルできたので起動してみます。
$ /usr/ports/x11/rxvt-unicode/work/stage/usr/local/bin/urxvt &
よさそうです (図 2)。ということで OS やウィンドウマネージャには依存せず、単に rxvt-unicode のバージョンアップによる現象であることがわかりました。後始末をして次に進みましょう。
# cd /usr/ports/x11/rxvt-unicode/
# make clean
# gitup ports
新旧ソースコードを比較する
次に、新旧のソースコードを取得して差分を取ってみます。rxvt-unicode の Makefile を見ると次のように書かれていたので、
MASTER_SITES= http://dist.schmorp.de/rxvt-unicode/%SUBDIR%/
MASTER_SITE_SUBDIR= . Attic
次のようにして rxvt-unicode-9.30.tar.bz2 および rxvt-unicode-9.31.tar.bz2 を取得し、展開、比較します (テキストベースで記録を残すためにコマンド多めでやっていますが、実際はもっと目に優しいツールを使ったほうがいいと思います)。
$ wget http://dist.schmorp.de/rxvt-unicode/Attic/rxvt-unicode-9.30.tar.bz2
$ wget http://dist.schmorp.de/rxvt-unicode/Attic/rxvt-unicode-9.31.tar.bz2
$ tar -xvf rxvt-unicode-9.30.tar.bz2
$ tar -xvf rxvt-unicode-9.31.tar.bz2
$ diff -ru rxvt-unicode-9.30/src/ rxvt-unicode-9.31/src/
ちなみに diff に-q
オプションを加えるとファイル名のみの表示に切り替わります。幸い変更点はそれほど多くなく、diff の行数で 700 行足らずでした。
この差分をもとにソースコードの当該部分を眺めていって、screen.C の rxvt_term::scr_reset() というメソッドにたどり着きました。公式の CVS リポジトリで screen.C の履歴を確認すると、2022 年 8 月 4 日にコミットされた Rev. 1.464 以降、数回にわたって当該部分が変更されているようです。コミットログが空欄のため意図は不明ですが…。
あやしい部分を旧バージョンに戻してみる
あやしい部分は見つかりましたが本当にここが原因なのでしょうか?確かめるためにここだけ 9.30 のコードに戻してみます。再び ports に戻って、次のように確認しました。
# cd /usr/ports/x11/rxvt-unicode/
# make extract
# vi work/rxvt-unicode-9.31/src/screen.C (ファイルを編集してあやしい部分を 9.30 相当に戻す)
# make
$ /usr/ports/x11/rxvt-unicode/work/stage/usr/local/bin/urxvt &
予想どおり、今度は問題ありません (図 2 と同じ結果)。これで原因部分を特定できました。
原因部分のソースコードを眺める
原因部分が特定できたので、その部分のコードを眺めて対策を考えます。その際「ENABLE_FRILLS」とか「Re-wrap」「wing」のようなよくわからない単語が出てきましたが、urxvt(8) のマニュアルにしっかり書かれていました。
ENABLE_FRILLS
通常では入力できないようなユニコード文字を Shift + Ctrl + 16 進数コードで入力できる機能らしい。「
--enable-frills
」を付けてコンパイルすると有効になる。Re-wrap
行の文字列がスクリーンの右端にかかったときにワードラップする (はみ出る部分の文字列を回り込ませる) かどうか。X リソースの
rewrapMode
または rxvt-unicode 起動時の-rm
オプションでauto
、always
、またはnever
を指定する。デフォルトはauto
。wing
ワードラップしない、つまりスクリーンの右端を突き抜ける、という意味っぽい。
それから、term_start
やtop_row
などのメンバ変数が登場しますが、それらの関係は rxvt.h 内のコメントに説明図がありました。
* | most coordinates are stored relative to term_start,
* ROW_BUF | which is the first line of the terminal screen
* |························= row_buf[0]
* |························= row_buf[1]
* |························= row_buf[2] etc.
* |
* +------------+···········= term_start + top_row
* | scrollback |
* | scrollback +---------+·= term_start + view_start
* | scrollback | display |
* | scrollback | display |
* +------------+·display·+·= term_start
* | terminal | display |
* | terminal +---------+
* | terminal |
* | terminal |
* +------------+···········= term_start + nrow - 1
これらを踏まえてソースコードを読むと、ターミナルスクリーンの 1 行目の位置を持っているというterm_start
や、それに関連しそうな計算を行っている次の処理
term_start = total_rows - nrow;
top_row = q - term_start;
// make sure all terminal lines exist
while (top_row > 0)
scr_blank_screen_mem (ROW (--top_row), DEFAULT_RSTYLE);
が、9.30 では Re-wrap モードがalways
のときだけ実行されるのに対し、9.31 ではモードに関係なく実行されるように変更されていました。と、いうことは、問題なしと思っていた 9.30 も Re-wrap モードがalways
のときは現象が発生するのでしょうか?と思って確認したところ、そうなりました (下表)。実は 9.30 でもalways
のときは現象が再現するものの、デフォルトのauto
で使用していたため、(私には) 露見していなかった、ということでした。
Re-wrap Mode | 9.30 | 9.31 |
---|---|---|
auto (default) | 〇 | × |
always | × | × |
never | 〇 | × |
〇: 未発生, ×: 発生 |
その後 (伝統的な)printf
デバッグを使って各変数の中身をチェックしてみたりしましたが、これ以上調べても私にはこの変更の意図が理解できそうになかったので、ソースコードの調査はここで打ち切りました。
メーリングリストをチェックする
さて、件の現象は仕様かもしれないものの、できれば直してほしいので、がんばって英文を書いて報告しようかなあと考えました。で、連絡先等を調べるために公式ホームページをいろいろまさぐってみたところ、メーリングリストで本件がすでに報告されていました。2023 年 1 月 8 日の rxvt at c4k3.net という方の投稿です。ただ、いまのところ (2023 年 5 月) この投稿に対するアクションはない模様です。
対策
方針
私としては、- いままでどおり Re-wrap モードが
auto
のときにプロンプトを一番上に表示してくれればいい。 - 変更の意図がわからないので、根本的に修正することはできない。
- 現象はメーリングリストを通じて開発陣に伝わっているっぽい。
作業記録
いままでの調査用の ports ツリーはなかったことにして、GitHub から clone したての状態から作業開始します。
まず rxvt-unicode のソースコードを取得。
# cd /usr/ports/x11/rxvt-unicode/
# make extract
# cd work/rxvt-unicode-9.31/
screen.C のバックアップ、編集、パッチ作成。パッチの中身は次の節に掲載します。
# cp src/screen.C src/screen.C.orig
# vi src/screen.C (当該部分を 9.30 相当に戻す)
# diff -u src/screen.C.orig src/screen.C > ../../files/patch-mypatch-src__screen.C
なお、パッチの作成にあたりいくつか注意点があります。詳細は FreeBSD port 作成者のためのハンドブック 第 4.4 節を参照してください。
- パッチはソースコードが展開されたディレクトリ (ここでは /usr/ports/x11/rxvt-unicode/work/rxvt-unicode-9.31/) からの相対パスとなるように作成し、
PATCHDIR
(ここでは /usr/ports/x11/rxvt-unicode/files/) に置きます。 - ファイル名は patch-<なんちゃら> というように付けます。
- screen.C に対する他のパッチがすでに存在する場合は注意が必要です (複数のパッチで同じファイルを修正するのは避ける)。今回はたまたま screen.C に対する他のパッチが存在しないので、何も考えずに作成しましたが、将来は事情が変わるかもしれません。
編集したファイルを (適当な) 別名に退避、元のファイルを復旧、パッチの適用、パッチ適用後のファイルが先ほど編集したファイルと一致する (差分がない) ことを確認。
# mv src/screen.C src/screen.C.930
# mv src/screen.C.orig src/screen.C
# cd ../../
# make patch
# diff work/rxvt-unicode-9.31/src/screen.C work/rxvt-unicode-9.31/src/screen.C.930
コンパイルして症状が発生しないことを確認し、インストール。
# make
$ /usr/ports/x11/rxvt-unicode/work/stage/usr/local/bin/urxvt &
# make install
作成したパッチ
最後に、作成したパッチを掲載しておきます。ソースコードの美しさよりも、変更点のわかりやすさを優先したつもりです (野良パッチなので…)。
--- src/screen.C.orig 2023-05-15 12:37:33.228000000 +0900
+++ src/screen.C 2023-05-15 13:06:39.678611000 +0900
@@ -389,10 +389,38 @@
scr_blank_line (*qline, qline->l, ncol - qline->l, DEFAULT_RSTYLE);
}
while (p != pend && q > 0);
+
+#define DG_TO_930
+#ifdef DG_TO_930
+ term_start = total_rows - nrow;
+ top_row = q - term_start;
+
+ // make sure all terminal lines exist
+ while (top_row > 0)
+ scr_blank_screen_mem (ROW (--top_row), DEFAULT_RSTYLE);
+#endif
}
else
#endif
+#ifdef DG_TO_930
{
+ // if no scrollback exists (yet), wing, instead of wrap
+
+ for (int row = min (nrow, prev_nrow); row--; )
+ {
+ line_t &src = prev_row_buf [MOD (term_start + row, prev_total_rows)];
+ line_t &dst = row_buf [row];
+
+ copy_line (dst, src);
+ }
+
+ for (int row = prev_nrow; row < nrow; row++)
+ scr_blank_screen_mem (row_buf [row], DEFAULT_RSTYLE);
+
+ term_start = 0;
+ }
+#else
+ {
// wing, instead of wrap
screen.cur.row += nrow - prev_nrow;
@@ -412,6 +440,7 @@
// make sure all terminal lines exist
while (top_row > 0)
scr_blank_screen_mem (ROW (--top_row), DEFAULT_RSTYLE);
+#endif
clamp_it (screen.cur.row, 0, nrow - 1);
clamp_it (screen.cur.col, 0, ncol - 1);