2011-01-20 25 views
54

Tôi đang làm việc trên một ứng dụng gia sư đánh máy cho Mac OSX cần phải có tổ hợp phím được chuyển tiếp đến nó, ngay cả khi ứng dụng không được lấy nét.OSX: Phát hiện các sự kiện keyDown trên toàn hệ thống?

Có cách nào để hệ thống chuyển tiếp các lần nhấn phím đến ứng dụng, có thể thông qua NSDistributedNotificationCenter không? Tôi đã googled bản thân mình ngớ ngẩn, và đã không thể tìm câu trả lời ...

EDIT:Mẫu mã dưới đây.

Cảm ơn @NSGod đã chỉ cho tôi đúng hướng - Tôi đã kết thúc việc thêm global events monitor bằng phương thức addGlobalMonitorForEventsMatchingMask: handler :, hoạt động tốt. Để hoàn chỉnh, thực hiện của tôi trông như thế này:

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

}]; 

Đối với tôi, phần khó khăn đã sử dụng khối, vì vậy tôi sẽ cung cấp cho một ít mô tả trong trường hợp nó giúp mọi người:

Những điều cần chú ý về ở trên mã là tất cả một phương thức gọi là trên NSEvent. Khối được cung cấp dưới dạng đối số, trực tiếp đến chức năng. Bạn có thể nghĩ nó giống như một phương thức đại biểu nội tuyến. Chỉ vì điều này đã mất một thời gian để chìm trong tôi, tôi sẽ làm việc qua từng bước ở đây:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask 

Bit đầu tiên này không có vấn đề gì. Bạn đang gọi một phương thức lớp trên NSEvent, và nói cho nó biết sự kiện nào bạn muốn theo dõi, trong trường hợp này là NSKeyDownMask. Bạn có thể tìm thấy list of masks for supported event typeshere.

Bây giờ, chúng ta đến với một phần khó khăn: xử lý, mà hy vọng một khối:

handler:^(NSEvent *event){ 

Nó đã cho tôi một vài lỗi biên dịch để có được quyền này, nhưng (cảm ơn bạn Apple) họ đã lỗi rất mang tính xây dựng tin nhắn. Điều đầu tiên cần chú ý là carat ^. Điều đó báo hiệu sự bắt đầu của khối. Sau đó, trong các dấu ngoặc đơn,

NSEvent *event 

Khai báo biến mà bạn sẽ sử dụng trong khối để nắm bắt sự kiện. Bạn có thể gọi nó là

NSEvent *someCustomNameForAnEvent 

không quan trọng, bạn sẽ chỉ sử dụng tên đó trong khối. Sau đó, đó chỉ là về tất cả để có nó. Đảm bảo đóng ngoặc xoăn của bạn, và khung để kết thúc cuộc gọi phương pháp:

}]; 

Và bạn đã hoàn tất! Điều này thực sự là loại 'một lót'. Việc bạn thực hiện cuộc gọi này trong ứng dụng của bạn ở đâu không quan trọng - tôi làm điều đó trong phương thức applicationDidFinishLaunching của AppDelegate. Sau đó, trong khối, bạn có thể gọi các phương thức khác từ bên trong ứng dụng của mình.

+1

Hey @Pirripli nhờ rất nhiều vì đã giải thích khối cú pháp cho tôi. REALLY giúp đỡ cho những người học tập! Hoạt động tuyệt vời bằng cách này :) – lab12

+1

Không sao cả. ;) Tôi luôn biết ơn khi những người khác làm như vậy cho tôi, vì vậy tôi cố gắng giúp đỡ khi tôi có thể. –

+0

Khi tôi thử khối mã này, tôi nhận được lỗi: 'Các kiểu con trỏ khối không tương thích gửi 'void (^) (struct NSEvent *)'' đến tham số kiểu void (^) (NSEvent *) 'trong khai báo - @Pirripli , Tôi đã quên thêm một cái gì đó? Bạn đã bao gồm một cái gì đó đặc biệt để hỗ trợ cú pháp khối? [Ảnh chụp màn hình] (https://img.skitch.com/20111024-tnmnka59j9fqn6u145g2jppk5d.png) – Tronathan

Trả lời

24

Nếu bạn đồng ý với yêu cầu tối thiểu OS X 10.6+ và có thể đủ quyền truy cập "chỉ đọc" vào luồng sự kiện, bạn có thể cài đặt trình giám sát sự kiện toàn cầu trong Cocoa: Cocoa Event-Handling Guide: Monitoring Events.

Nếu bạn cần hỗ trợ OS X 10.5 và trước đó, và truy cập chỉ đọc là okay, và không nhớ làm việc với Carbon Event Manager, về cơ bản bạn có thể làm tương đương Carbon bằng cách sử dụng GetEventMonitorTarget(). (Bạn sẽ khó có thể tìm thấy bất kỳ tài liệu (chính thức) nào về phương pháp đó). API đó lần đầu tiên có sẵn trong OS X 10.3, tôi tin.

Nếu bạn cần quyền truy cập đọc-ghi vào luồng sự kiện, khi đó bạn sẽ cần phải xem API cấp thấp hơn một chút là một phần của ApplicationServices> CoreGraphics: CGEventTapCreate() và bạn bè. Đây là lần đầu tiên có sẵn trong 10.4. Lưu ý rằng tất cả 3 phương pháp sẽ yêu cầu người dùng phải bật "Bật quyền truy cập cho thiết bị trợ giúp" trong cửa sổ Tùy chọn hệ thống> Tùy chọn truy cập toàn cục (ít nhất là cho các sự kiện chính).

+0

Tuyệt vời, điều này có vẻ tuyệt vời! Tôi sẽ xem liệu tôi có thể chuẩn bị và chạy vào ngày mai, và sau đó chấp nhận! Một câu hỏi: Có cách nào tôi có thể kiểm tra xem người dùng có bật quyền truy cập hay không, ví dụ: để cho phép tôi nhắc họ làm như vậy? –

+1

Bạn có thể làm điều này bằng cách sử dụng tập lệnh táo. Hãy thử tập lệnh bên dưới. tell ứng dụng "Hệ thống Sự kiện" để thiết lập isUIScriptingEnabled đến các yếu tố giao diện người dùng kích hoạt nếu isUIScriptingEnabled = false thì \t tell ứng dụng "System Preferences" \t \t kích hoạt \t \t thiết lập cửa sổ hiện tại để pane "com.apple.preference.universalaccess" \t \t hiển thị hộp thoại "Hãy chọn \" cho phép truy cập cho các thiết bị trợ giúp \ "hộp kiểm và khởi động lại" \t \t trở \t cuối nói kết thúc nếu –

3

Vấn đề tôi thấy với điều này là bất kỳ khóa nào được đăng ký toàn cầu bởi một ứng dụng khác sẽ không được mua ... hoặc ít nhất là trong trường hợp của tôi, có lẽ tôi đang làm điều gì sai.

Nếu chương trình của bạn cần hiển thị tất cả các phím, chẳng hạn như "Command-Shift-3" chẳng hạn, khi đó chương trình sẽ không thấy nó đi qua để hiển thị nó ... vì hệ điều hành được hệ điều hành lấy.

Hoặc ai đó đã tìm ra điều đó? Tôi rất muốn biết ...

+2

bạn có thấy solut ion này? Tôi cũng khá hứng thú với nó. –

14

Tôi đang đăng mã hoạt động cho trường hợp của mình.

Tôi đang thêm trình xử lý sự kiện toàn cầu sau khi ứng dụng khởi chạy. Phím tắt của tôi làm cho ctrl + alt + cmd + T mở ứng dụng của tôi.

- (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

Vì NSGod đã chỉ ra rằng bạn cũng có thể sử dụng CoreGraphics.

Trong lớp học của bạn (ví dụ như trong 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(); 

Bên ngoài của lớp, nhưng trong cùng một tập tin .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

Cảm ơn bạn đã đăng bài chi tiết! Làm việc tuyệt vời. – vaughan

+0

Gọi '[NSEvent eventWithCGEvent: event]' để nhận một 'NSEvent' với một API đẹp hơn để làm việc với mã ObjC. Trong ARC, bạn cần phải kết nối. Ví dụ: '[(__bridge MyClass *) refcon myHandlerInstanceMethod: e];' – vaughan

Các vấn đề liên quan