読者です 読者をやめる 読者になる 読者になる

のらぬこの日常を描く

ノージャンルのお役立ち情報やアニメとゲームの話、ソフトウェア開発に関する話などを中心としたブログです。

GNU screenでcaptionの日本語が文字化けする問題を直してみた

Linux 技術・開発

ある日ちょっと思いました。

私は、普段mplayer*1 とかでバックグラウンドで音楽再生*2してるんですが

再生中の曲をscreenのcaption領域に出せたらちょっと便利じゃね?

やりかたとしては

  • mplayerのコンソール出力から曲名部分をいい感じに取り出す。
  • 取り出した曲名を screen -X eval 'caption always "...."' あたりで設定してやる

てことで比較的簡単に出来そう。

一番の問題は、GNU screenの captionに日本語入れると文字化けすることか。

この文字化けはずいぶんから出てるのだが*3、Fedora12になっても一向に解決される気配はない。

検索しても解決策が出てこないので*4、ソースハックしてみることにした。

・・・・・・

・・・・・・

・・・・・orz...

これは、ダメだろw

設定ファイルちょろっとさわれば直るとかそういうレベルじゃない

せっかくなので、修正してみた。


f:id:mikenekoDX:20100207155524p:image

ものすごい制限付きだけど一応動いたようなので patchを貼っておく。

なお、patch は Fedora12のソースリポジトリから引っ張ってきたソースに対して作成してる。

screen 4.0.3 ベースのようだが、もともとなんかパッチが当たってるみたい。

制限:

  1. encoding : utf8でのみ有効。
  2. U+0080~U+0FFFのコード範囲は考慮してない。
  3. caption のみ対応。hardstatus は未修正
  4. フォントレンダリング(文字の色とか)の位置がずれる場合あり。
  5. マルチバイト文字の途中で、れんだりんぐを変更することができない。
  6. padding(%=)が2つ以上あると動かない。・・・っていうか元の動きがどうなのかよく分からない。
  7. captionに直接指定した文字のみ正しく機能する。曜日(%D)とかはうまくいかないかも。

考察とか

caption行と、hardstatusline行については、とにかく 【strlen(str) == 表示上の桁数】が大前提で組まれてた。

表示上の桁数とstrlen()の戻り値が異なるutf-8環境では、そのズレを考慮しないで、表示桁数バイトのUTF-8文字列を作り、それをそのまま流し込んだ場合、最後の桁まで書いたつもりが実は最後までかけてなかったってことが起こる。

オリジナル版で曜日が3バイトの化け文字で表示されてたのも、化けてて読めないけど位置はずれてないからとりあえずこれでいいでしょ?的に妥協したとしか読み取れない。

私がやった修正はすごくやっつけで、

キャプション描画描画時のみ、utf-8のマルチバイト文字の先頭バイト(に見える)コードが現れたら表示桁サイズ(と関連する一時変数の値)をその都度ふやすというもの。

ほんとは表示位置<=>文字のバイト位置のテーブル作って、それを見ながら描画位置とレンダリング位置を調整していけばよかったんだろうけど、ちょっとそこまでやる元気がなかった。


diff -Naur screen-4.0.3-old/display.c screen-4.0.3/display.c
--- screen-4.0.3-old/display.c	2003-12-05 22:45:41.000000000 +0900
+++ screen-4.0.3/display.c	2010-02-07 15:09:00.312674139 +0900
@@ -874,13 +874,68 @@
 RAW_PUTCHAR(c)
 int c;
 {
-  ASSERT(display);
+#ifdef UTF8
+	static int utfPhase = 0;
+	static int utfLength = 0;
+	static char utfBuffer[4] = 
+		{ '\0', '\0', '\0', '\0' };
+	static int utfIsError = 0;
+#endif
+
+	ASSERT(display);
 
 #ifdef FONT
 # ifdef UTF8
   if (D_encoding == UTF8)
     {
-      c = (c & 255) | (unsigned char)D_rend.font << 8;
+		if( D_rendering_status )
+		{
+			if( ( c & 0xc0 ) == 0x80 )
+			{
+				if( ( utfLength == 0 ) && ( utfIsError == 0 ) )
+				{
+					c = ' ';
+					utfIsError = 1;
+				}
+				else if( utfLength == 0 )
+				{
+					return;
+				}
+				else
+				{
+					utfPhase ++;
+					if( utfPhase < utfLength )
+						utfBuffer[utfPhase] = c;
+					if( utfPhase == utfLength - 1 )
+					{
+						int index = 0;
+						for( index = 0; index < utfLength; index ++ )
+							AddChar( utfBuffer[index] );
+						utfPhase = 0;
+						utfLength = 0;
+						return;
+					}
+					else
+					{
+						return;
+					}
+				}
+			}
+			else
+			{
+				utfPhase = 0;
+				utfBuffer[utfPhase] = c;
+				if( ( c & 0x80 ) == 0 )
+					utfLength = 1;
+				else if( ( c & 0xe0 ) == 0xc0 )
+					utfLength = 2;
+				else if( ( c & 0xf0 ) == 0xe0 )
+					utfLength = 3;
+				if( utfLength > 1 )
+					return;
+			}
+		}
+		c = (c & 255) | (unsigned char)D_rend.font << 8;
 #  ifdef DW_CHARS
       if (D_mbcs)
 	{
diff -Naur screen-4.0.3-old/display.h screen-4.0.3/display.h
--- screen-4.0.3-old/display.h	2003-07-01 23:01:42.000000000 +0900
+++ screen-4.0.3/display.h	2010-02-07 15:09:04.363674270 +0900
@@ -187,6 +187,7 @@
   int   d_blankerpid;
   struct event d_blankerev;
 #endif
+  int d_rendering_status;	/* rendering caption / status */
 };
 
 #ifdef MULTI
@@ -301,8 +302,7 @@
 #define D_idleev	DISPLAY(d_idleev)
 #define D_blankerev	DISPLAY(d_blankerev)
 #define D_blankerpid	DISPLAY(d_blankerpid)
-
-
+#define D_rendering_status DISPLAY(d_rendering_status)
 #define GRAIN 4096	/* Allocation grain size for output buffer */
 #define OBUF_MAX 256	/* default for obuflimit */
 
diff -Naur screen-4.0.3-old/screen.c screen-4.0.3/screen.c
--- screen-4.0.3-old/screen.c	2010-02-07 10:20:11.362800177 +0900
+++ screen-4.0.3/screen.c	2010-02-07 15:09:12.667673989 +0900
@@ -2050,33 +2050,83 @@
 int numpad;
 int padlen;
 {
-  char *pn, *pn2;
-  int i, r;
-
-  padlen = padlen - (p - buf);	/* space for rent */
-  if (padlen < 0)
-    padlen = 0;
-  pn2 = pn = p + padlen;
-  r = winmsg_numrend;
-  while (p >= buf)
-    {
-      if (r && p - buf == winmsg_rendpos[r - 1])
+	if( ( D_encoding != UTF8 ) && ( ( numpad == 0 ) || ( numpad > 1 ) ) )
 	{
-	  winmsg_rendpos[--r] = pn - buf;
-	  continue;
+		char *pn, *pn2;
+		int i, r;
+		padlen = padlen - (p - buf);	/* space for rent */
+		if (padlen < 0)
+			padlen = 0;
+		pn2 = pn = p + padlen;
+		r = winmsg_numrend;
+		while (p >= buf)
+	    {
+			if (r && p - buf == winmsg_rendpos[r - 1])
+			{
+				winmsg_rendpos[--r] = pn - buf;
+				continue;
+			}
+			*pn-- = *p;
+			if (*p-- == 127)
+			{
+				pn[1] = ' ';
+				i = numpad > 0 ? (padlen + numpad - 1) / numpad : 0;
+				padlen -= i;
+				while (i-- > 0)
+					*pn-- = ' ';
+				numpad--;
+			}
+		}
+		return pn2;
 	}
-      *pn-- = *p;
-      if (*p-- == 127)
+	else
 	{
-	  pn[1] = ' ';
-	  i = numpad > 0 ? (padlen + numpad - 1) / numpad : 0;
-	  padlen -= i;
-	  while (i-- > 0)
-	    *pn-- = ' ';
-	  numpad--;
+		int topRight = 0;
+		int lengthMove = 0;
+		int div = 0;
+		int index = 0, index2 = 0;
+		int lengthRight = 0;
+		int lengthTarget = 0;
+		lengthTarget = p - buf;
+		p = buf;
+		for( index = 0; index < padlen; index ++ )
+		{
+			if( p[index] == 127 )
+				break;
+		}
+		p[index] = ' ';
+		index ++;
+		div = index;
+		for( ; index < lengthTarget; index ++ )
+		{
+			if( ( p[index] & 0x80 ) == 0 )
+			{
+				lengthRight ++;
+			}
+			else if( ( p[index] & 0xe0 ) == 0xc0 )
+			{
+				lengthRight ++;
+				index++;
+				padlen ++;
+			}
+			else if( ( p[index] & 0xf0 ) == 0xe0 )
+			{
+				lengthRight += 2;
+				index += 2;
+				padlen ++;
+			}
+		}
+		topRight = padlen - ( lengthTarget - div );
+		lengthMove = topRight - div;
+		if( lengthMove > 0 )
+		{
+			for( index = lengthTarget - 1, index2 = 0; index >= div; index --, index2 ++ )
+				p[padlen - (index2 + 1)] = p[index];
+			for( index = div; index < topRight; index ++ )
+				p[index] = ' ';
+		}
+		return p;
 	}
-    }
-  return pn2;
 }
 
 struct backtick {
@@ -2809,6 +2859,8 @@
 
   if (s != winmsg_buf)
     return 0;
+
+  D_rendering_status = 1;
   rend = D_rend;
   p = 0;
   l = strlen(s);
@@ -2818,47 +2870,74 @@
       if (p > winmsg_rendpos[i] || winmsg_rendpos[i] > l)
 	break;
       if (p < winmsg_rendpos[i])
-	{
-	  n = winmsg_rendpos[i] - p;
-	  if (n > max)
-	    n = max;
-	  max -= n;
-	  p += n;
-	  while(n-- > 0)
-	    {
-	      if (start-- > 0)
-		s++;
-	      else
-	        PUTCHARLP(*s++);
-	    }
-	}
-      r = winmsg_rend[i];
-      if (r == -1)
-	{
-	  if (rendstackn > 0)
-	    rend = rendstack[--rendstackn];
-	}
+	  {
+		  n = winmsg_rendpos[i] - p;
+		  if (n > max)
+			  n = max;
+		  max -= n;
+		  p += n;
+		  while (n-- > 0)
+		  {
+			  if (start-- > 0)
+			  {
+				  s++;
+			  }
+			  else
+			  {
+				  if( D_encoding == UTF8 )
+				  {
+					  /* multibyte char start */
+					  if( ( (*s) & 0xf0 ) == 0xe0 )
+					  {
+						  max ++;
+						  l ++;
+					  }
+				  }
+				  PUTCHARLP (*s++);
+			  }
+		  }
+	  }
+	  r = winmsg_rend[i];
+	  if (r == -1)
+	  {
+		  if (rendstackn > 0)
+			  rend = rendstack[--rendstackn];
+	  }
       else
+	  {
+		  rendstack[rendstackn++] = rend;
+		  ApplyAttrColor (r, &rend);
+	  }
+      SetRendition (&rend);
+  }
+	if (p < l)
 	{
-	  rendstack[rendstackn++] = rend;
-	  ApplyAttrColor(r, &rend);
-	}
-      SetRendition(&rend);
-    }
-  if (p < l)
-    {
-      n = l - p;
-      if (n > max)
-	n = max;
-      while(n-- > 0)
-	{
-	  if (start-- > 0)
-	    s++;
-	  else
-	    PUTCHARLP(*s++);
+		n = l - p;
+		if (n > max)
+			n = max;
+		while (n-- > 0)
+		{
+			if (start-- > 0)
+			{
+				s++;
+			}
+			else
+			{
+				if( D_encoding == UTF8 )
+				{
+					if( ( (*s) & 0xf0 ) == 0xe0 )
+					{
+						max ++;
+						l ++;
+						n ++;
+					}
+				}
+				PUTCHARLP (*s++);
+			}
+		}
 	}
-    }
-  return 1;
+	D_rendering_status = 0;
+	return 1;
 }
 
 

*1WindowswmpじゃなくてLinuxのやつね

*2:$ mplayer -shuffle -playlist playlist.txt >/dev/null 2>&1 &

*3:よく見るのは screenの caption行で、%Dで曜日のとこが化けるのですが・・・ってやつ。そして結局未解決のまま %Dを使わない方向に落ち着く。私もそうだったし。

*4:hardstatusのみ、位置がずれるけどとりあえず表示はできる的なパッチなら見つけたが