キーイベントを他アプリに送る

PC-8801エミュレータ*1のウィンドウにキーイベントを送って、テキストファイルに書かれたN88-BASICプログラムをPC-8801に入力するという無理矢理なプログラム。しょーもないプログラムだけど、何かに応用できることもあるかもしれないので挙げておきます。


まず、FindWindow関数でウィンドウのハンドルを取得します。ウィンドウのハンドルは、アプリのクラス名またはウィンドウタイトルの文字列を指定して取得しますが、今回のPC-8801エミュレータの場合、ウィンドウタイトルにフレームレートやクロック周波数が表示されて時々刻々変化するので、指定に用いることができません。そこでクラス名を指定しないといけないわけですが、アプリのクラス名はツールを使って調べることができます。今回はwinfoというツールを使いました。


SendMessage関数でキーイベントを送ります。日本語キーボード環境なので、記号類の仮想キーコードが少し分かりにくいことになっています。また、PC-8801の反応が遅いので、キーダウンとキーアップの間にかなり長いスリープを挟んでいます。もし、キーイベントでなく文字入力イベントを送ればいいのであれば、WM_IME_KEYDOWN / WM_IME_KEYUP イベントの代わりに WM_IME_CHAR イベントを用います。

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char line[1000];
    unsigned int i;
    char c;
    FILE *fp;
    bool shift;

    // ソースファイルを開く
    fp = fopen("source.bas","r");
    if(fp == NULL){
        printf("source file not found!\n");
        system("pause");
        return 1;
    }

    // ウィンドウハンドルを取得
    HWND hWnd = FindWindow("M88p2 WinUI", NULL);
    if(hWnd == NULL)
    {
        printf("M88 not found!\n");
        system("pause");
        return 2;
    }

    //ウィンドウタイトルを取得
    TCHAR title[256] = "";
    GetWindowText(hWnd, title, 256);
    printf("%s\n", title);

    //ウィンドウを前面に
    SetForegroundWindow(hWnd);

    //ソースを1行ずつ読み取る
    while (fgets(line, 1000, fp) != NULL)
    {
        //1文字ずつ読み取る
        for(i=0;i<strlen(line);i++){
            c = line[i];
            shift = false;
            //シフトキーが必要な文字
            if     (c=='!') { shift = true; c='1';}
            else if(c=='"') { shift = true; c='2';}
            else if(c=='#') { shift = true; c='3';}
            else if(c=='$') { shift = true; c='4';}
            else if(c=='%') { shift = true; c='5';}
            else if(c=='&') { shift = true; c='6';}
            else if(c=='\''){ shift = true; c='7';}
            else if(c=='(') { shift = true; c='8';}
            else if(c==')') { shift = true; c='9';}
            else if(c=='=') { shift = true; c='-';}
            else if(c=='{') { shift = true; c='[';}
            else if(c=='}') { shift = true; c=']';}
            else if(c=='+') { shift = true; c=';';}
            else if(c=='*') { shift = true; c=':';}
            else if(c=='<') { shift = true; c=',';}
            else if(c=='>') { shift = true; c='.';}
            else if(c=='?') { shift = true; c='/';}
            else if(c=='~') { shift = true; c='^';}
            // 特別なキーコード
            if(c=='-') c=(char)VK_OEM_MINUS;
            if(c=='[') c=(char)VK_OEM_4;
            if(c==']') c=(char)VK_OEM_6;
            if(c==':') c=(char)VK_OEM_1;
            if(c==';') c=(char)VK_OEM_PLUS;
            if(c==',') c=(char)VK_OEM_COMMA;
            if(c=='.') c=(char)VK_OEM_PERIOD;
            if(c=='/') c=(char)VK_OEM_2;
            if(c=='@') c=(char)VK_OEM_3;
            if(c=='\\')c=(char)VK_OEM_5;
            if(c=='^') c=(char)VK_OEM_7;
            // シフトキー(押す)を送る
            if(shift){
                SendMessage(hWnd,WM_IME_KEYDOWN,VK_SHIFT,1);
                Sleep(100);
            }
            // キー(押す)を送る
            SendMessage(hWnd,WM_IME_KEYDOWN,c,1);
            Sleep(100);
            // キー(離す)を送る
            SendMessage(hWnd,WM_IME_KEYUP,  c,0);
            // シフトキー(離す)を送る
            if(shift){
                SendMessage(hWnd,WM_IME_KEYUP,VK_SHIFT,1);
            }
            Sleep(100);
        }
        // エンターキー(押す)を送る
        SendMessage(hWnd,WM_IME_KEYDOWN,VK_RETURN,1);
        Sleep(100);
        // エンターキー(離す)を送る
        SendMessage(hWnd,WM_IME_KEYUP,  VK_RETURN,0);
        Sleep(100);
    }
    // ソースファイルを閉じる
    fclose(fp);

    system("pause");
    return 0;
}



M88実行画面

*1:M88というエミュレータを使っています。