2011-01-20 11 views
54

Pracuję nad aplikacją pisania na klawiaturze dla systemu Mac OSX, która musi mieć przekierowane do niej klawisze, nawet gdy aplikacja nie jest aktywna.OSX: wykrywanie zdarzeń keyDown obejmujących cały system?

Czy istnieje sposób, aby system przesuwał do przodu sekwencje klawiszy, prawdopodobnie poprzez NSDistributedNotificationCenter? Mam google sobie głupie, i nie były w stanie znaleźć odpowiedź ...

EDIT:Przykładowy kod poniżej.

Dzięki @NSGod za skierowanie mnie we właściwym kierunku - skończyło się na dodaniu global events monitor przy użyciu metody addGlobalMonitorForEventsMatchingMask: handler :, która działa pięknie. Dla kompletności, moja realizacja wygląda następująco:

// 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); 

}]; 

Dla mnie najtrudniejsza część została przy użyciu bloków, więc dam trochę opis w przypadku, gdy ktoś pomaga:

Rzeczą godną uwagi o powyższy kod jest taki, że to wszystko pojedyncza metoda wywołania na NSEvent. Blok jest dostarczany jako argument, bezpośrednio do funkcji. Można by pomyśleć, że jest to metoda inline delegate. Tylko dlatego, że to trwało długo zatopić się za mnie, idę do pracy przez to krok po kroku tutaj:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask 

Ten pierwszy bit nie jest problemem. Nazywasz metodę klasy w NSEvent i mówisz, które zdarzenie chcesz monitorować, w tym przypadku NSKeyDownMask. A list of masks for supported event types można znaleźć here.

Teraz dochodzimy do skomplikowanej części: obsługi, która spodziewa się blok:

handler:^(NSEvent *event){ 

zajęło mi kilka kompilacji błędów, aby uzyskać to prawo, ale (dziękuję Apple) były bardzo konstruktywne błąd wiadomości. Pierwszą rzeczą, którą należy zauważyć, jest karat ^. To sygnalizuje początek bloku. Następnie w nawiasach podano deklarację zmiennej, która będzie używana w bloku do przechwytywania zdarzenia. Można to nazwać, nie ma znaczenia, po prostu użyjesz tej nazwy w bloku. W takim razie wszystko jest w porządku. Pamiętaj, aby zamknąć nawias klamrowy i wspornik, aby zakończyć wywołanie metody:

}]; 

I gotowe! To naprawdę jest rodzaj "jednego liniowca". Nie ma znaczenia, gdzie wykonasz połączenie wewnątrz aplikacji - robię to w aplikacji AppDelegate applicationDidFinishLaunching. Następnie w ramach bloku możesz wywoływać inne metody z poziomu swojej aplikacji.

+1

Hej @Pirripli dzięki bardzo za wyjaśnienie blok składnia dla mnie. NAPRAWDĘ pomaga tym, którzy się uczą! Świetnie spisuje się przy okazji :) – lab12

+1

Bez problemu. ;) Zawsze jestem wdzięczny, gdy inni robią to dla mnie, więc staram się pomóc, kiedy mogę. –

+0

Kiedy próbuję tego bloku kodu, pojawia się błąd: "Niezgodne typy wskaźnika bloku wysyłającego" void (^) (struct NSEvent *) "do parametru typu void (^) (NSEvent *)" w deklaracji - @Pirripli , Czy zapomniałem coś dodać? Czy musisz dodać coś specjalnego do obsługi składni blokowej? [Screenshot] (https://img.skitch.com/20111024-tnmnka59j9fqn6u145g2jppk5d.png) – Tronathan

Odpowiedz

24

Jeśli spełniasz minimalne wymagania systemu OS X 10.6+ i możesz uzyskać dostęp "tylko do odczytu" do strumienia zdarzeń, możesz zainstalować globalny monitor zdarzeń w kakao: Cocoa Event-Handling Guide: Monitoring Events.

Jeśli potrzebujesz wsparcia dla OS X 10.5 i wcześniej, a dostęp tylko do odczytu jest w porządku i nie ma nic przeciwko pracy z Menedżerem zdarzeń Carbon, możesz w zasadzie zrobić ekwiwalent węgla za pomocą GetEventMonitorTarget(). (Będzie ci ciężko znaleźć jakąkolwiek (oficjalną) dokumentację na temat tej metody). Ten interfejs API był po raz pierwszy dostępny w systemie OS X 10.3, jak sądzę.

Jeśli potrzebujesz dostępu do odczytu i zapisu do strumienia zdarzeń, musisz przyjrzeć się nieco niższemu poziomowi API, który jest częścią ApplicationServices> CoreGraphics: CGEventTapCreate() i przyjaciółmi. Najpierw był dostępny w wersji 10.4.

Należy pamiętać, że wszystkie 3 metody wymagają, aby użytkownik miał włączoną opcję "Włącz dostęp dla urządzeń pomocniczych" w preferencjach System Preferences> Universal Access (przynajmniej dla zdarzeń kluczowych).

+0

Fantastyczny, wygląda świetnie! Zobaczę, czy uda mi się sprawić, że wszystko zacznie działać jutro, a potem zaakceptuj! Jedno pytanie: czy istnieje sposób, w jaki mogę sprawdzić, czy użytkownik ma włączony dostęp, np. aby pozwolić mi to zachęcić? –

+1

Możesz to zrobić za pomocą skryptu Apple. Wypróbuj poniższy skrypt. Tell „Zdarzenia systemowe” aplikacji na ustawianie isUIScriptingEnabled do elementów UI włączona jeśli isUIScriptingEnabled = false następnie \t Tell aplikacji „Preferencje systemowe” \t \t aktywują \t \t ustawić aktualną okienka do okienka „com.apple.preference.universalaccess” \t \t wyświetlacz dialogowe „wybierz \” Włącz dostęp dla urządzeń wspomagających \ „pole wyboru i uruchom ponownie” \t \t powrót \t koniec powiedzieć koniec jeśli –

3

Problem z tym, że każdy klucz zarejestrowany globalnie przez inną aplikację nie zostanie złapany ... lub przynajmniej w moim przypadku, być może robię coś nie tak.

Jeśli twój program potrzebuje wyświetlić wszystkie klawisze, np. "Command-Shift-3" na przykład, to nie zobaczy go, aby wyświetlić ... ponieważ jest pobierany przez system operacyjny.

Czy ktoś to wymyślił? Chciałbym wiedzieć ...

+2

czy znaleźliście SOLUT do tego? Ja też jestem tym zainteresowany. –

14

Zamieszczam kod, który zadziałał w mojej sprawie.

Dodaję globalną procedurę obsługi zdarzeń po uruchomieniu aplikacji. Mój skrót powoduje, że Ctrl + Alt + cmd + T otwiera moją aplikację.

- (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]; 
     } 
    }]; 

} 
3

Jak już wspomniano NSGod, można również użyć CoreGraphics.

W swojej klasie (np -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(); 

Poza klasy, ale w tym samym pliku .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; 
} 
+0

Dziękuję za szczegółowy post! Pracował świetnie. – vaughan

+0

Wywołanie '[NSEvent eventWithCGEvent: event]' pozwala uzyskać 'NSEvent' z ładniejszym API do pracy z kodem ObjC. W ARC musisz się mostkować. Przykład: '[(__bridge MyClass *) refcon myHandlerInstanceMethod: e];' – vaughan

Powiązane problemy