keyhac でウィンドウ階層を指定する

keyhac v1.08 ではウィンドウ別のキーマップを定義できる。たとえば explorer.exe の Edit コントロールにフォーカスがあるときのキーマップはこのように定義する。

keymap_explorer = keymap.defineWindowKeymap(exe_name=u"explorer.exe", class_name=u"Edit") 

しかし、explorer.exe ではアドレスバーも検索ボックスもリネーム中のファイル名もみんな Edit コントロールなのだ。class_name="" では末端のコントロール名だけが照合対象となるので、あちこちにある Edit コントロールを互いに区別できない。
窓使いの憂鬱では、トップレベルウィンドウから末端のコントロールまでのウィンドウ階層をコロン区切りで表した文字列に対して正規表現でマッチングを行うことができる。たとえばこんな感じ。

window ExplorerEditControl /explorer\.exe:(.+:)*Edit$/ : Global

これと同じことを keyhac で行うには次のようにする。

# config.py
from keyhac import *
import re

def match_window(pattern):
    def _match_window(wnd):
        exe_name = wnd.getProcessName() 
        lineage = ":" + wnd.getClassName() + ":"
        wnd = wnd.getParent()
        while wnd.getParent():
            lineage = ":" + wnd.getClassName() + lineage
            wnd = wnd.getParent()
        lineage = exe_name + lineage
        print "match_window: " + re.sub("(?i)(" + pattern + ")", ur"<\1>", lineage)
        return re.search(pattern, lineage, re.IGNORECASE)
    return _match_window

def configure(keymap):
    keymap_global = keymap.defineWindowKeymap()
    
    def launch_selstr(keymap, prefix = u"", suffix = u""):
        def _launch_selstr():
            keymap.command_InputKey("C-C")()
            shellExecute(None, None, prefix + getClipboardText() + suffix, "", "", SW_NORMAL)
        return _launch_selstr
    
    keymap_explorer = keymap.defineWindowKeymap(check_func=match_window(ur"explorer\.exe:.*:Edit:$"))

この match_window 関数を使うと窓使いの憂鬱と同じように正規表現を使ってウィンドウ階層を特定することができる。たとえば Vistaエクスプローラでは

アドレスバー
explorer.exe:CabinetWClass:WorkerW:ReBarWindow32:Address Band Root:msctls_progress32:ComboBoxEx32:ComboBox:Edit:
検索バー
explorer.exe:CabinetWClass:WorkerW:ReBarWindow32:UniversalSearchBand:Search Control:Edit:
ツリービューでリネーム中
explorer.exe:CabinetWClass:ShellTabWindowClass:DUIViewWndClassName:DirectUIHWND:CtrlNotifySink:NamespaceTreeControl:SysTreeView32:Edit:
リストビューでリネーム中
explorer.exe:CabinetWClass:ShellTabWindowClass:DUIViewWndClassName:DirectUIHWND:CtrlNotifySink:SHELLDLL_DefView:SysListView32:Edit:

となるから、リネーム中の Edit コントロールだけを対象にしたければ

    keymap_explorer = keymap.defineWindowKeymap(check_func=match_window(ur"explorer\.exe:CabinetWClass:.*:Edit:$"))

とでもすればよい。*1

*1:窓使いの憂鬱と違って実行ファイル名はパスを含まない。また、文字列の最後にもコロンが付くので注意。