檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
ところで、アーカイブってけっこう便利ですよ。タクソノミーも作成中。今は疲れるので作っていません。

2009-12-22 (火)

画面をクリアするために、Win32 APIをPythonから呼ぶハメになった

| 10:18 | 画面をクリアするために、Win32 APIをPythonから呼ぶハメになったを含むブックマーク

Catyのコンソールシェルは、名前の通りコンソールウィンドウで動くので、しばらく使っていれば、プロンプトがコンソールウィンドウの一番下に降りてしまいます。画面クリアして、カーソルとプロンプトを最上段に持ってきたいですよね。

僕のごときジイサマだと、画面クリアといえば、ANSIエスケープシーケンス ESC, '[', '2', 'J' を思い起こします。で、print "\x1b[2J" とやってみました。が、Windowsのコンソールウィンドウ(コマンドプロンプト)ではダメでした。ええー、最近のWindowsコンソールはANSIエスケープシーケンスをサポートしてないのぉー。

ちょっと探したら、WConio -- Windows CONsole I/O for Pythonというライブラリが見つかったのですが、これの実体は WConiomodule.c というCモジュールです。特に画面クリアは次の関数。

// clear the screen
static int int_clrscr (void)
{
    HANDLE hConOut;
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    COORD coord;
    DWORD dwDummy;

    hConOut = GetConOut();
    if (hConOut != INVALID_HANDLE_VALUE)
    {
        if (GetConsoleScreenBufferInfo (hConOut, &csbi)) {
            coord.X = 0;
            coord.Y = 0;
            FillConsoleOutputCharacter (hConOut, (TCHAR)' ', 
                    csbi.dwSize.X * csbi.dwSize.Y, coord, &dwDummy);
            FillConsoleOutputAttribute (hConOut, csbi.wAttributes, 
                    csbi.dwSize.X * csbi.dwSize.Y, coord, &dwDummy);
            SetConsoleCursorPosition (hConOut, coord);
        }
        ReleaseConOut (hConOut);
        return 0;
    }
    return -1;
}

Cで書いたライブラリをダイナミックリンクするのはあんまり嬉しくないなー。と思っていたら、ctypesというPythonモジュールを使えば、Windows APIPythonから直接叩けることを知りました。試してみるべ。Bring Colors to the Windows Console with Python という記事が参考になりました。これをパクっています。

ctypesの力を借りて、上のC関数を直訳したのが下のPythonコードです。

# -*- coding: utf-8 -*- 
# win32cls.py
#

from ctypes import windll, Structure, c_short, c_ushort, c_uint, byref

# Win32の型の定義: 
# SHORT, WORD, DWORD, 
# COORD, SMALL_RECT, CONSOLE_SCREEN_BUFFER_INFO

SHORT = c_short
WORD = c_ushort
DWORD = c_uint

class COORD(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("X", SHORT),
    ("Y", SHORT)]

class SMALL_RECT(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("Left", SHORT),
    ("Top", SHORT),
    ("Right", SHORT),
    ("Bottom", SHORT)]

class CONSOLE_SCREEN_BUFFER_INFO(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("dwSize", COORD),
    ("dwCursorPosition", COORD),
    ("wAttributes", WORD),
    ("srWindow", SMALL_RECT),
    ("dwMaximumWindowSize", COORD)]

# 定数の定義 winbase.h

STD_OUTPUT_HANDLE = -11

# Win32の変数と関数

stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)

GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition

# 画面クリア

def clear_screen ():
  csbi = CONSOLE_SCREEN_BUFFER_INFO()
  coord = COORD() # 初期値 coord.X = 0, coord.Y = 0
  dwDummy = DWORD()

  hConOut = stdout_handle
  if GetConsoleScreenBufferInfo (hConOut, byref(csbi)):
    FillConsoleOutputCharacterA (hConOut, 32, 
                                 csbi.dwSize.X * csbi.dwSize.Y, 
                                 coord, byref(dwDummy))
    FillConsoleOutputAttribute (hConOut, csbi.wAttributes, 
                                csbi.dwSize.X * csbi.dwSize.Y, 
                                coord, byref(dwDummy))
    SetConsoleCursorPosition (hConOut, coord)

右から左に翻訳しただけで、メカニズムの詳細を理解してはいないのですが、動くには動きました。

ともかくも、Catyシェルの画面クリアがなくて不便していたので、次のようにしてCatyコマンドに仕立てました。

# -*- coding: utf-8 -*- 
# cons.py (console.py はshellで使ってます)
#
from caty.command import Command
import sys
if sys.platform == 'win32':
    from win32cls import clear_screen
else:
    def clear_screen():
        pass # スタブ

# Catyコマンド Cls

class Cls(Command):
    def execute(self, input):
        clear_screen()
        return None

/* -*- coding: utf-8 -*- 
 * cons.casm (conて名前はWindowsでは使えないよ)
 */
module cons;

@console 
command cls :: void -> void
refers python:cons.Cls;

console, conて名前が安易に使えない点に注意してくださいね。Catyコンソールシェルからは、cons:cls として使っています*1。画面クリアは、OSシェルとは関係ないので、cygwin環境でもWindows標準コンソールを使っている限り機能します。こんなとき、ctypesが便利なのはよく分かりましたが、ANSIエスケープシーケンスのほうが簡単だったよなー、とかジイサマは思うのでした。Windows以外で同じことやるなら、termcap/terminfo, cursesとか使うことになるだろうし、あーメンド。

[追記]実は、上記ソースコードの次の部分は現状ではうまく動きません。

if sys.platform == 'win32':
    from win32cls import clear_screen

win32cls.pyとcons.pyを CATY_HOME/common/commands/ に放り込むとして、コマンドローダーがcons.pyを読んでメモリ内に配備するとき、win32cls.py は見えてないので、importが失敗します。これを回避するには、事前にPython自体のモジュールサーチパスに CATY_HOME/common/commands/ を入れておく必要があります。[/追記]

*1:モジュール名修飾無しの cls とすることもできますが、現在、仕様が若干曖昧です。

WackyWacky 2009/12/29 23:54 こんばんは。

エスケープシーケンスですが、SYSTEM32のCONFIG.NTにANSI.SYSドライバを設定する事で、利用できますよ。

ハウツー解説: Windows NT で ANSI.SYS を使用するには
http://support.microsoft.com/kb/100394/ja

m-hiyamam-hiyama 2009/12/31 14:55 Wackyさん、ありがとうございます。
年を越す前にご返答。

> エスケープシーケンスですが、SYSTEM32のCONFIG.NTにANSI.SYSドライバを設定する事で、利用できますよ。
やってみました。
\WINDOWS\system32\CONFIG.NTに:

device=%SystemRoot%\system32\ansi.sys

と追記してみたのですが、僕の環境では、command.comでもcmd.exeでもうまくいきませんでした。
なんか足りないのかな?