web-dev-qa-db-ja.com

OSX:システム全体のkeyDownイベントを検出しますか?

私は、Mac OSX用のタイピングチューターアプリケーションに取り組んでいます。アプリケーションにフォーカスが合っていなくても、キーストロークを転送する必要があります。

NSDistributedNotificationCenterを介して、システムにキーストロークをアプリに転送させる方法はありますか?私は愚かな自分をグーグルで調べましたが、答えを見つけることができませんでした...

EDIT:以下のサンプルコード

私を正しい方向に向けてくれてありがとう@NSGod---addGlobalMonitorForEventsMatchingMask:handler:メソッドを使用して グローバルイベントモニター を追加することになりました。完全を期すために、私の実装は次のようになります。

// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                       handler:^(NSEvent *event){

    NSString *chars = [[event characters] lowercaseString];
    unichar character = [chars characterAtIndex:0];

    NSLog(@"keydown globally! Which key? This key: %c", character);

}];

私にとって、トリッキーな部分はブロックを使用していたので、誰かに役立つ場合に備えて少し説明します。

上記のコードについて注意すべきことは、NSEventでのすべての1つのメソッド呼び出しであるということです。ブロックは、関数に直接to引数として提供されます。インラインデリゲートメソッドのようなものと考えることができます。これが私のために沈むのに時間がかかったという理由だけで、私はそれをステップバイステップでここで作業するつもりです:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask

この最初のビットは問題ありません。 NSEventでクラスメソッドを呼び出して、監視するイベント(この場合はNSKeyDownMask)を通知しています。 サポートされているイベントタイプのマスクのリスト は、 here にあります。

さて、トリッキーな部分に来ます:ハンドラーはブロックを期待します:

handler:^(NSEvent *event){

これを正しくするには、コンパイルエラーが少しかかりましたが、(Appleに感謝)非常に建設的なエラーメッセージでした。最初に気づくのはカラットです^。それはブロックの開始を知らせます。その後、括弧内で、

NSEvent *event

これは、イベントをキャプチャするためにブロック内で使用する変数を宣言します。あなたはそれを呼び出すことができます

NSEvent *someCustomNameForAnEvent

問題ではありません。ブロック内でその名前を使用するだけです。次に、それですべてです。必ず中括弧を閉じ、ブラケットを呼び出してメソッド呼び出しを終了してください。

}];

これで完了です!これは一種の「ワンライナー」です。アプリ内でこの呼び出しをどこで実行するかは関係ありません。AppDelegateのapplicationDidFinishLaunchingメソッドで実行します。次に、ブロック内で、アプリ内から他のメソッドを呼び出すことができます。

57
Chris Ladd

OS X 10.6+の最小要件に問題がなく、イベントストリームへの「読み取り専用」アクセスで十分であれば、Cocoaにグローバルイベントモニターをインストールできます。 Cocoa Event-Handling Guide:監視イベント

OS X 10.5以前をサポートする必要があり、読み取り専用アクセスが問題なく、Carbon Event Managerでの作業を気にしない場合、基本的にGetEventMonitorTarget()を使用してCarbonに相当することができます。 (ただし、その方法に関する(公式)ドキュメントを見つけるのは困難です)。このAPIはOS X 10.3で最初に利用可能になったと思います。

イベントストリームへの読み取り/書き込みアクセスが必要な場合は、ApplicationServices> CoreGraphics: CGEventTapCreate() and friendsの一部であるやや低レベルのAPIを確認する必要があります。これは10.4で最初に利用可能になりました。

3つすべての方法では、ユーザーが[システム設定]> [ユニバーサルアクセス]設定ペインで(少なくとも主要なイベントについて)[支援デバイスへのアクセスを有効にする]を有効にする必要があります。

24
NSGod

私のケースに合ったコードを投稿しています。

アプリの起動後にグローバルイベントハンドラーを追加しています。ショートカットを使用すると、ctrl + alt + cmd + Tでアプリが開きます。

- (void) applicationWillFinishLaunching:(NSNotification *)aNotification
{
    // Register global key handler, passing a block as a callback function
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                           handler:^(NSEvent *event){

        // Activate app when pressing cmd+ctrl+alt+T
        if([event modifierFlags] == 1835305 && [[event charactersIgnoringModifiers] compare:@"t"] == 0) {

              [NSApp activateIgnoringOtherApps:YES];
        }
    }];

}
14
micho

私がこれで見つけた問題は、別のアプリによってグローバルに登録されたキーが取得されないことです...または少なくとも私の場合、おそらく私は何か間違ったことをしています。

たとえば、「Command-Shift-3」のようにプログラムがすべてのキーを表示する必要がある場合、OSによって使用されるため、それを表示するのに時間がかかることはありません。

それとも誰かがそれを理解しましたか?知りたい...

4
Duane

NSGodがすでに指摘したように、CoreGraphicsも使用できます。

あなたのクラスで(例えば-initで):

CFRunLoopRef runloop = (CFRunLoopRef)CFRunLoopGetCurrent();

CGEventMask interestedEvents = NSKeyDown;
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 
                                        0, interestedEvents, myCGEventCallback, self);
// by passing self as last argument, you can later send events to this class instance

CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, 
                                                           eventTap, 0);
CFRunLoopAddSource((CFRunLoopRef)runloop, source, kCFRunLoopCommonModes);
CFRunLoopRun();

クラスの外部、ただし同じ.mファイル内:

CGEventRef myCGEventCallback(CGEventTapProxy proxy, 
                             CGEventType type, 
                             CGEventRef event, 
                             void *refcon)
{

    if(type == NX_KEYDOWN)
    {
       // we convert our event into plain unicode
       UniChar myUnichar[2];
       UniCharCount actualLength;
       UniCharCount outputLength = 1;
       CGEventKeyboardGetUnicodeString(event, outputLength, &actualLength, myUnichar);

       // do something with the key

       NSLog(@"Character: %c", *myUnichar);
       NSLog(@"Int Value: %i", *myUnichar);

       // you can now also call your class instance with refcon
       [(id)refcon sendUniChar:*myUnichar];
   }

   // send event to next application
   return event;
}
3
Sitses