公開日: 2023年5月19日

rxvt-unicode-9.31 でプロンプトの表示位置がおかしい件

rxvt-unicode を最新版の 9.31 に上げたところ、シェルのプロンプトが画面の真ん中あたりに表示されるという問題 (または仕様?) に見舞われました。安易ですが、該当する部分だけソースコードを旧バージョンに戻してコンパイルすることで対処しましたので、その調査過程と作業内容を記録として残しておきます。私自身の備忘録でもあるために回りくどいことをやっていますが、手っ取り早く対策だけ知りたいという方は、対策の項まで読み飛ばしてください。

現象

rxvt-unicode を起動するとシェルのプロンプトがおかしな位置に表示される (図 1)。
図 1: なんかおかしい
図 1: なんかおかしい
発生環境:
  • バージョン: 9.311)
  • OS: FreeBSD 13.2-RELEASE1)
  • ウィンドウマネージャ: awesome 4.3_3,12)
  1. FreeBSD 12.2-RELEASE で rxvt-unicode 9.30 を使用していたときは、このような現象はありませんでした。
  2. twm でも確認したところ、一見問題なさそうだったのですが、ウィンドウをリサイズすると再現しました。というわけで、ウィンドウマネージャには依存しません。

調査記録

元のバージョンにダウングレードする

以前 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: よさそう
図 2: よさそう

よさそうです (図 2)。ということで OS やウィンドウマネージャには依存せず、単に rxvt-unicode のバージョンアップによる現象であることがわかりました。後始末をして次に進みましょう。

# cd /usr/ports/x11/rxvt-unicode/
# make clean
# gitup ports

新旧ソースコードを比較する

次に、新旧のソースコードを取得して差分を取ってみます。rxvt-unicode の Makefile を見ると次のように書かれていたので、

/usr/ports/x11/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オプションでautoalways、またはneverを指定する。デフォルトはauto

  • wing

    ワードラップしない、つまりスクリーンの右端を突き抜ける、という意味っぽい。

それから、term_starttop_rowなどのメンバ変数が登場しますが、それらの関係は rxvt.h 内のコメントに説明図がありました。

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や、それに関連しそうな計算を行っている次の処理

screen.C (抜粋)
          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 Mode9.309.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);