PDICの機能を利用するPythonスクリプト

DDEを使ってPDICを操作するPythonスクリプトを作ってみた。要 Python Win32 Extensions
なんかもう、Win32にべったりでアレな感じなんだけども。

pdictool search word

PDIC を Foreground にして word を入力した状態に。

pdictool popup word

でポップアップ検索ができる。
py2exeでWindows用のバイナリ*1を作っておいたので、こんなのでも欲しい人はご自由に。

しかし、PDIC の DDE はパラメータを Null Terminate しておかないといけないのな。ついでに戻り値も Null Terminate されているんだけど。小一時間ハマってしまったよ。

import sys
import win32ui, dde
import win32con, win32process, win32gui, win32event
import pywintypes
import _winreg

VERSION = "0.1"
DEFAULT_DDE_SERVICE_NAME = "PDICW"
DDE_CLIENT_NAME = "PDICClient"
REG_PATH = "SOFTWARE\\ReliefOffice\\CurrentVersion\\App Paths\\PDICW32.EXE"
EXEC_WAIT_TIME = 500

_SERVER = None

class PDICError(Exception):
    def __init__(self, message):
	self.message = message
    
    def __str__(self):
	return str(self.message)

class PDIC:

    def __init__(self, service=DEFAULT_DDE_SERVICE_NAME, topic = "PDIC"):
	global _SERVER
	if _SERVER is None:
	    _SERVER = dde.CreateServer()
	    _SERVER.Create(DDE_CLIENT_NAME)
	self.conversation = dde.CreateConversation(_SERVER)
	try:
	    self.conversation.ConnectTo(service, topic)
	except:
	    if sys.exc_info()[0] != 'error':
		raise
	    self._exec_pdic(service)
	    self.conversation.ConnectTo(service, topic)

    def Request(self, name):
	return self.conversation.Request(name)[:-1]

    def Poke(self, name, param):
	self.conversation.Poke(name, param + "\x00")

    def Exec(self, name):
	self.conversation.Exec(name)

    def _exec_pdic(self, service, wait = EXEC_WAIT_TIME):
	cmd = self._get_pdic_path()
	si = win32process.STARTUPINFO()
	si.dwFlags = win32con.STARTF_USESHOWWINDOW
	si.wShowWindow = win32con.SW_SHOWNOACTIVATE | win32con.SW_MINIMIZE 
	
	cmdline = cmd + " -e " + shell_escape(service)
	try:
	    handles = win32process.CreateProcess(
		None,
		cmdline,
		None,
		None,
		1,
		win32process.CREATE_NEW_CONSOLE,
		None,
		None,
		si)
	except pywintypes.error:
	    raise PDICError("Cannot execute " + cmd)
    
	if handles and wait != 0:
	    win32event.WaitForInputIdle(handles[0], wait)

	if handles:
	    return True
	else:
	    return False
	

    def _get_pdic_path(self):
	try:
	    key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, REG_PATH)
	except WindowsError:
	    raise PDICError("PDIC not found")
	else:
	    val = _winreg.QueryValue(key, "")
	    key.Close()
	    return val

def shell_escape(str):
    replaces = ('"', "'", ' ')
    for replace in replaces:
	str = str.replace(replace, '\\' + replace)
    return str

def focus():
    pdic = PDIC(topic = "PDIC")
    handle = int(pdic.Request("GetMainWindow"))
    win32gui.SetForegroundWindow(handle)
    win32gui.ShowWindow(handle, win32con.SW_RESTORE)
    
def main():
    from optparse import OptionParser
    commands = ["popup", "search"]

    def popup(word, options):
	dict = PDIC(service = options.service_name, topic = "Dictionary")
	dict.Poke("Open", "")
	dict.Poke("PopupSearch", word)

    def search(word, options):
	simulate = PDIC(service = options.service_name, topic = "Simulate")
	simulate.Poke("InputWord", word)
	focus()

    usage = "usage: %%prog [options] (%s) word" % "|".join(commands)
    parser = OptionParser(usage = usage, version = VERSION)
    parser.add_option("-s", "--service", dest="service_name",
		      help="DDE service name", metavar="service",
		      default=DEFAULT_DDE_SERVICE_NAME)

    (options, args) = parser.parse_args()

    if len(args) < 2:
	parser.print_help()
	sys.exit(1)

    command = args[0]
    if command not in commands:
	parser.print_help()
	sys.exit(1)
    
    word = " ".join(args[1:])
    eval(command)(word, options)


if __name__ == '__main__':
    try:
	main()
    except PDICError, error:
	print >>sys.stderr, error

*1:というかそもそもWindowsでないと実行できないんだけど