姫踊子草からkeyhacに乗り換えました

キー入力入れ替えソフト「姫踊子草」情報頁
姫踊子草(XP)はタイプウェルを打つ分には何も問題なかったのですが、WeatherTypingやTODといったDirectXを使ったソフトで正しく打鍵できないという問題があります。AZIKの練習自体はほとんどタイプウェルでしかやらなかったので、あまり問題はなかったのですが、やはりプレイできないタイトルがあるというのはどうにかしたいと思っていました。


keyhac - craftware
そこで見つけたのがkeyhac。pythonで実装されているということもあって以前から目をつけていたのですが、過去の版ではマルチストローク(n打鍵にm打鍵を割り当てる)には対応していませんでした。久しぶりに見たところ対応版が出ていたので、早速使ってみたらWeatherTypingでは正しく動きました!

keyhacの面白いところは設定ファイルがpythonで書かれていて、任意の組み合わせのキーが押されたときに任意の処理を行う(別のキー入力をエミュレートする、アプリケーションを起動するetc)という動作を簡単に定義することができることです*1。やろうと思えば、入力中に動的に挙動を変えるキーボード配列も実現できちゃいます(意味があるかはまだ未検討ですが)。

キーカスタマイズにとどまらずランチャーにもできるし、設定ファイルの書き方でいろんな楽しみ方ができるソフトですね。素晴らしいです!

ちなみに公式サンプルのマルチストロークの定義の仕方が非常にまだるっこしいので、以下のようなタブ区切りのファイルで定義できるように設定ファイルを書いたので置いときます。
↓定義例(タブ区切りで入力と出力を定義します)

kgp kyou
yp yp

↓が設定ファイル(config.py)の内容です。MULTI_STROKE_FILEで指定したファイルに上の定義を1行ずつ書いておけばマルチストロークが実現できます。これはこれで長ったらしくなっていますが、コピペでOKです。

from keyhac import *

MULTI_STROKE_FILE = "multistroke.txt"

def configure(keymap):
  keymap_global = keymap.defineWindowKeymap()
  def_list = load_definitions(MULTI_STROKE_FILE)
  define_multi_strokes(def_list, keymap_global, keymap)

def load_definitions(filename):
  definitions = []
  fp = open(filename, "r")
  for line in fp:
    params = line.strip().split("\t")
    if len(params) == 1:
      continue
    input, output = params
    definitions.append([input, output])
  fp.close()
  return definitions

# 環境によって以下の記号に対応する仮想キーが違う場合があります
keyname_trans = {
    "-": "MINUS", "+": "ADD", ",": "COMMA", ".": "PERIOD",
    "/": "(191)", "*": "MULTIPLY", " ": "SPACE", ";": "(187)",
    ":": "(186)", "\t": "TAB", "@": "(192)", "[": "(219)",
    "\\": "(220)", "]": "(221)", "^": "(222)"
}

def define_multi_strokes(define_list, target_keymap, keymap):
  """
  # [[input1,output1],[input2,output2], ...]
  define_list: [["tt", "tati"], ["a", "a"], ...]
  """
  for input, output in define_list:
    define_multi_stroke(input, output, target_keymap, keymap)

def define_multi_stroke(input, output, target_keymap, keymap):
  if not input:
    return

  if not isinstance(input, list):
    input = replace_vk(input)

  key = input[0].upper()

  if len(input) == 1:
    output = replace_vk(output)
    target_keymap[key] = keymap.command_InputKey(*output)
  else:
    try:
      new_map = target_keymap[key]
    except KeyError:
      new_map = keymap.defineMultiStrokeKeymap(key)
      target_keymap[key] = new_map
    define_multi_stroke(input[1:], output, new_map, keymap)

def replace_vk(output):
  if isinstance(output, list):
    # リストの場合はそのまま返す
    return output

  # 文字列の場合は文字ごとに分解する
  # その際、keyhacで認識できない記号や仮想キーコードを正しく処理する
  vk_mode = False
  vk_list = []
  result = []
  for e in output:
    e = keyname_trans.get(e, e)
    if e == '(':
      vk_mode = True
      continue
    if vk_mode:
      if e == ')':
        vk_mode = False
        result.append("".join(vk_list))
        vk_list = []
        continue
      vk_list.append(e)
    else:
      result.append(e)
  return result

*1:ただしPythonを少し知る必要があります