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

レガシーコード生産ガイド

私に教えられることなら

Squeak5 on Windows7で日本語インライン入力に対応させる

www.youtube.com

FFIを利用します。

環境はWindows7(64bit)です。

C言語、WindowsAPI、Smalltalkすべてについて初心者なので何か凄まじく危ないことをしているかもしれません。真似しておかしくなっても自己責任でお願いします。助言など大歓迎です。

手順など

  • 日本語表示フォントを入れる。別の記事参照
  • FFIライブラリをインストール
  • IME操作用のメソッド群を用意
  • ImmWin32>>#keyboardFocusForAMorph:で使用

何をしているか

WindowsAPIのIME関連(imm32.dll)関数を使って、変換ウインドウの位置を変更しています。The Design and Implementation of Multilingualized Squeak(PDF)を参考にしました。

ひっかかった点

  • Win32Handleがちゃんとハンドルを取得できてない?
    • HWND(Win32Window)やHIMCなどハンドルを利用する場面はvoid*で対処
      • 最近のWindowsではHANDLEはvoid*らしい(?)
  • 操作終了後に(おそらく同スレッド内で)必ずImmReleaseContextを呼ぶ

1.FFIライブラリインストール

How to Use FFIの通り、以下のコードを選択してdoItすればSqueak5では簡単に入ります。

(Installer repository: 'http://source.squeak.org/FFI')
    install: 'FFI-Pools';
    install: 'FFI-Kernel';
    install: 'FFI-Tests';
    install: 'FFI-Examples'.

2.メソッド群を用意

とりあえず再利用などは無視して、適当なクラスを作ってクラスメソッドで手続き的に書きます。もっと適したクラスなどがあったら教えてください。

今回はクラス名InlineInputterとしました。

作るメソッドは次の4つのFFI

  • ウインドウのハンドル(hWND)取得
  • インプットメソッドコンテキストのハンドル(hIMC)取得
  • 入力欄の位置設定
  • hIMC解放

と、#keyboardFocusForAMorph:から呼ぶためのメソッドの5つです。

それから、入力欄の位置設定のために構造体を1つ定義する必要があります。

ハンドル取得/解放

まずウインドウのハンドル(hWND)の取得です。FFI-Examples-Win32のgetFocusを変更したものを作ります。(元のものは上手く使えませんでした)

InlineInputter class>> getFocus
    <apicall: void* 'GetFocus' () module: 'user32.dll'>
    ^self externalCallFailed

次に、そのhWNDを渡してインプットメソッドコンテキストのハンドル(hIMC)を取得するメソッドです。

InlineInputter class>> getContext: hWND
    <apicall: void* 'ImmGetContext' (void*) module: 'imm32.dll'>
    ^self externalCallFailed

hWND、hIMCを渡してコンテキストを解放させるメソッドも作ります。

InlineInputter class>> releaseOn: hWND context: hIMC
    <apicall: bool 'ImmReleaseContext' (void* void*) module: 'imm32.dll'>
    ^self externalCallFailed

構造体定義

メインの入力欄の位置設定に渡す構造体を定義します。

構造体は、

  1. ExternalStructureのサブクラスを作り
  2. クラスメソッドfieldsを定義して
  3. クラスにdefineFieldsメソッドを送る

と、サイズを調整済みのアクセサが自動で生成/変更されます。

クラス名は自由です。ここではWin32CompositionFormとします。

茜色に染まる夏 COMPOSITIONFORM構造体のdwStyleメンバについてを参考にして、fieldsを次のように定義します。

Win32CompositionForm class>> fields
    ^#((dwStyle 'ulong') (ptCurrentPos 'Win32Point') (rcArea 'Win32Rectangle'))

その後、Win32CompositionForm defineFieldsをdoItしてアクセサが作られたことを確認します。

表示位置設定

まずFFIメソッドを定義します。

InlineInputter class>> apiSetCompositionWindow: ctx as: cf
    <apicall: bool 'ImmSetCompositionWindow' (void* Win32CompositionForm*) module: 'imm32.dll'>
    ^self externalCallFailed

Win32CompositionFormへのポインタを渡すことに気をつけます。

そして、座標を渡して設定するためのメソッドを定義して完成です。

InlineInputter class>> setCompositionWindowX: x y: y
    | wnd ctx cf pt |
    wnd := self getFocus.
    ctx := self getContext: wnd.
    pt := Win32Point new.
    pt x: x; y: y.
    cf := Win32CompositionForm new.
    cf 
        dwStyle: 2; "CFS_POINT。ちゃんと参照したほうがいいかも"
        ptCurrentPos: pt;
        rcArea: (Win32Rectangle new). "空でいい"
    self apiSetCompositionWindow: ctx as: cf.
    self releaseOn: wnd context: ctx.

3.使用

ImmWin32>>#keyboardFocusForAMorph:を次のように1行書き換えます。Squeak5をWindowsで起動するとこれが呼ばれますが、どうもImmプラグインが無いのでタイプの度にエラーが出ては無視されているみたいです……

keyboardFocusForAMorph: aMorph
    
    aMorph ifNil: [^ self].
    [
        | left top pos |
        pos := aMorph preferredKeyboardPosition.
        left := (pos x min: Display width max: 0) asInteger.
        top := (pos y min: Display height max: 0) asInteger.
        InlineInputter setCompositionWindowX: left y: top "こ↑こ↓"
    ] on: Error
    do: [:ex |].

以上です。セーブした瞬間からインライン入力ができるようになっていると思います。

今後

Linux MintではIMEがそもそも起動できませんでしたが、どうにかできそうな気もしてきました。(WindowsではImmSetOpenStatusでオンオフできます)

カーソル位置変更の度に毎回呼ばれるので、HWNDなどは適宜キャッシュした方がいいかもしれません。

とりあえずこれでSqueakをフル活用できそうな気がします。FFIが対応していれば今後のバージョンでもしばらくは大丈夫でしょう。

広告を非表示にする