[C++] 370 ローマ字を英数モードで打った時のリカバリー その2 かなキー連続押下対応

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

前回のコードでは、かなキーを何となく押し、英字を入力して無意識に2回目を押したりすると不要なひらがなが一気に出現します。

これではまずいので1秒以内に2回目を押した時だけひらがな変換するようにしました。

あとはひらがな変換した文字列を範囲選択して再変換するという処理ですが、流石にC++でもハードルは高そうです。

Carbonフレームワークが今も現役なことに少々驚きました。

#include <Carbon/Carbon.h>
#include <iostream>
#include <string>
#include <unordered_map>
#include <chrono>

std::unordered_map<CGKeyCode, std::string> keyCodeMap = {
    {0, "a"}, {11, "b"}, {8, "c"}, {2, "d"}, {14, "e"}, {3, "f"},
    {5, "g"}, {4, "h"}, {34, "i"}, {38, "j"}, {40, "k"}, {37, "l"},
    {46, "m"}, {45, "n"}, {31, "o"}, {35, "p"}, {12, "q"}, {15, "r"},
    {1, "s"}, {17, "t"}, {32, "u"}, {9, "v"}, {13, "w"}, {7, "x"},
    {16, "y"}, {6, "z"}
};

std::string inputBuffer;
int kanaKeyPressCount = 0;
std::chrono::steady_clock::time_point firstKanaKeyPressTime;

CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
    if (type == kCGEventKeyDown) {
        CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

        if (keyCode == 104) { // かなキー(104)が押された場合
            std::cout << "かなキーを押しました" << std::endl;
            auto now = std::chrono::steady_clock::now();

            if (kanaKeyPressCount == 0) {
                firstKanaKeyPressTime = now;
                kanaKeyPressCount = 1;
            } else if (kanaKeyPressCount == 1) {
                auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - firstKanaKeyPressTime).count();
                if (duration <= 1) { // 1秒以内に2回目が押された場合
                    std::string command = "osascript \"LatinToHiragana.applescript\" " + inputBuffer;
                    std::cout << "command: " << command << std::endl;
                    system(command.c_str()); // コマンドを実行

                    inputBuffer.clear();
                    std::cout << "AppleScript実行。Current Bufferをリセット" << std::endl;
                } else {
                    std::cout << "1秒以上経過のため、カウントをリセット" << std::endl;
                }
                kanaKeyPressCount = 0; // カウントをリセット
            }
        } else {
            auto it = keyCodeMap.find(keyCode);
            if (it != keyCodeMap.end()) {
                inputBuffer += it->second;
                std::cout << "Current Buffer: " << inputBuffer << std::endl;
            } else {
                inputBuffer.clear();
                std::cout << "Current Bufferをリセット" << std::endl;
            }
        }
    }

    return event;
}

int main() {
    CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyDown), eventCallback, nullptr);
    if (!eventTap) {
        std::cerr << "Failed to create event tap" << std::endl;
        return 1;
    }

    CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
    CGEventTapEnable(eventTap, true);
    CFRunLoopRun();

    return 0;
}

[C++] 369 ローマ字を英数モードで打った時のリカバリー その1 Swiftから移植 AppleScript

[Mac M2 Pro 12CPU, Sonoma 14.3.1, clang++ 15.0.0]

Swift版ではアプリ起動時に入力モードが固定されてしまうため、新たに入力モードを設定するコードを追加する必要がありました。しかし、これが切り替え遅延の原因となり最初の入力文字を取りこぼしてしまいます。

Swiftでは限界を感じたのでC++に移植しました。アプリを起動しても入力モードの切り替えは可能です。遅延もありません。

Swiftはアプリ商品の開発にだけ使用するのが無難ですね。非常にセキュアですが、自由度が低すぎます。Javaと似たような感じです。

#include <Carbon/Carbon.h>
#include <iostream>
#include <string>
#include <unordered_map>

// キーボードキーと文字の対応
std::unordered_map<CGKeyCode, std::string> keyCodeMap = {
    {0, "a"}, {11, "b"}, {8, "c"}, {2, "d"}, {14, "e"}, {3, "f"},
    {5, "g"}, {4, "h"}, {34, "i"}, {38, "j"}, {40, "k"}, {37, "l"},
    {46, "m"}, {45, "n"}, {31, "o"}, {35, "p"}, {12, "q"}, {15, "r"},
    {1, "s"}, {17, "t"}, {32, "u"}, {9, "v"}, {13, "w"}, {7, "x"},
    {16, "y"}, {6, "z"}
};

std::string inputBuffer;
int kanaKeyPressCount;

CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
    if (type == kCGEventKeyDown) {
        CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

        // かなキー(104)が2回連続で押された場合の処理
        if (keyCode == 104) {
            std::cout << "かなキーを押しました" << std::endl; 
            kanaKeyPressCount += 1;

            if (kanaKeyPressCount == 2){
                // AppleScriptを実行する
                std::string command = "osascript \"LatinToHiragana.applescript\" " + inputBuffer;
                std::cout << "command: " << command << std::endl; 
                system(command.c_str());
                
                kanaKeyPressCount = 0;
            }
        } else {
            // キーコードに対応する文字がある場合、inputBufferに追加
            auto it = keyCodeMap.find(keyCode);
            if (it != keyCodeMap.end()) {
                inputBuffer += it->second;
                std::cout << "Current Buffer: " << inputBuffer << std::endl;
            } else {
                inputBuffer.clear();
                std::cout << "Current Bufferをリセット" << std::endl;
            }
        }
    }

    return event;
}

int main() {
    // CGEventTapCreateの呼び出しでkCGEventTapOptionDefaultを使用
    CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyDown), eventCallback, nullptr);
    if (!eventTap) {
        std::cerr << "Failed to create event tap" << std::endl;
        return 1;
    }

    CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
    CGEventTapEnable(eventTap, true);
    CFRunLoopRun();

    return 0;
}