2013年12月31日火曜日

OSX-10.9でソフトウェアによる仮想的なキーボード入力をデスクトップに行う方法

私は開発環境としてのOSXは嫌いです。それはさておき、件について早速メモを残します。

サンプルソースはgithubにまとめてあります。
#include <thread>
#include <chrono>
#include <iostream>

#include "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGEvent.h"
#include "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGEventSource.h"
#include "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGEventTypes.h"
#include "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CGRemoteOperation.h"

int main()
{
  constexpr auto key_press_code   = true;
  constexpr auto key_release_code = false;
  
  constexpr auto keycode = CGKeyCode(38);
  
  auto event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
  
  auto key_press_event   = CGEventCreateKeyboardEvent(event_source, keycode, key_press_code);
  auto key_release_event = CGEventCreateKeyboardEvent(event_source, keycode, key_release_code);
  
  //CGEventSetFlags(key_press_event, kCGEventFlagMaskShift);
  //CGEventSetFlags(key_release_event, kCGEventFlagMaskShift);
  
  for(auto n : {5,4,3,2,1})
  {
    std::cout << "count down: " << n << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  
  constexpr auto event_tap_location = kCGHIDEventTap;
  
  CGEventPost(event_tap_location, key_press_event);
  CGEventPost(event_tap_location, key_release_event);
  
  CFRelease(key_press_event);
  CFRelease(key_release_event);
}

AppleのC APIドキュメントを参考に5秒のカウントダウン後に'j'キーをソフトウェアにより仮想的に押して離したかのような効果を与える簡単なサンプルです。コード的には特に問題無いでしょう。

実行結果として、コンソールで実行すればjと勝手にキータイプされるでしょうし、5秒のカウントダウン中に端末エミュレーターを離れて他のアプリの入力可能な場所をフォーカスすれば、そこにjと勝手にキータイプされます。

(何回かに一度、タイプされない事があるかもしれませんが、CGEventPostの先の非同期性か或いはpress/releaseの間隔の問題かもしれません。)

問題はCGEventCreateKeyboardEventのシンボル、バイナリーコードが実際に定義されたライブラリーファイルはどこの何か、という事でした。これで少々時間を取られました。

CMakeLists.txt で find_file によって /System/Library/Frameworks/***.framework/Versions/AをHINTSにCoreGraphicsであるとかCoreFoundationであるとかのファイルをチェックしてtarget_link_librariesに放り投げています。

つまり、これらのファイルがOSXの提供するC APIのバイナリーコードを含んだ共有ライブラリーという事です。当初は何らかの .dylib ファイルに定義されているのだろうと、find|nm|grepで探してみましたが見当たりません。システム全体の.dylibや念の為に.aなどもfind対象にしてみても _CGEventCreateKeyboardEvent などのシンボルが見つかりません。これはどうも妙だと思い、もしかしたら.dylibを付けずにぽんっとファイルをどこかに置いてるのではないかと探してみるとすぐに怪しいファイルの存在に気付いたという落ちです。

さて、ここで聡明な、或いは経験豊富なOSX開発者の方は、そもそもOSX版のclang++には-frameworkというものあって云々とおっしゃるのでしょうが、私はそういうの嫌いなので、C APIはC APIらしくnmで定義を確認して普通にC++らしくリンクしたかったのです。

でも、この少々の寄り道のお陰でOSXが-frameworkというものをどういう仕組みで提供しているのかも大凡見当が付きました。仕様書を片っ端から読んで覚える時間があったなら、先にそうして把握しているのでしょうけどね。私はこういう愚かな勉強が大好きです。

ps. お行儀よくするなら-frameworkを素直に使えと言うのはさておき、せめて"CGEvent.h"などもcmakeからfind_fileなりfind_pathしてinclude_directoriesで対応した方が綺麗だったかな。気が向いたら対応します。

参考:

  1. MAC Developer Library - Quartz Event Services Reference
    1. https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html#//apple_ref/c/func/CGEventCreateKeyboardEvent
    2. https://developer.apple.com/library/mac/documentation/corefoundation/Reference/CFTypeRef/Reference/reference.html
  2. stackoverflow - Simulate keypress for system wide hotkeys

0 件のコメント:

コメントを投稿