2015-09-10 13 views

Trả lời

13

Tôi thực sự giải quyết vấn đề này bản thân mình chỉ là ngày khác. Tôi đã viết một số blog post trên đó, cũng như một số Gist

Tôi sẽ nhúng bài đăng trên blog và mã cuối cùng trong trường hợp blog hoặc Gist không bao giờ biến mất. Lưu ý: Đây là một bài viết rất dài trình bày chi tiết về cách lớp học được xây dựng và những gì bạn có thể làm để gọi các phương thức khác trong đại biểu của ứng dụng của bạn. Nếu tất cả những gì bạn muốn là sản phẩm đã hoàn thành (lớp MediaApplication), hãy tiến về phía dưới cùng. Nó nằm ngay trên XML và Info.plist informaton.


Để bắt đầu, để nhận các sự kiện chính từ các phím phương tiện bạn cần để tạo lớp mở rộng NSApplication. Đây là đơn giản như

import Cocoa 

class MediaApplication: NSApplication { 
} 

Tiếp theo, chúng ta cần phải ghi đè lên sendEvent() chức năng

override func sendEvent(event: NSEvent) { 
    if (event.type == .SystemDefined && event.subtype.rawValue == 8) { 
     let keyCode = ((event.data1 & 0xFFFF0000) >> 16) 
     let keyFlags = (event.data1 & 0x0000FFFF) 
     // Get the key state. 0xA is KeyDown, OxB is KeyUp 
     let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA 
     let keyRepeat = (keyFlags & 0x1) 
     mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(keyRepeat)) 
    } 

    super.sendEvent(event) 
} 

Bây giờ, tôi không giả vờ hoàn toàn hiểu những gì đang xảy ra ở đây, nhưng tôi nghĩ rằng tôi có một ý tưởng tốt. Các đối tượng NSEvent chứa một số thuộc tính chính: type, subtype, data1data2. Typesubtype khá tự giải thích, nhưng data1data2 vô cùng mơ hồ. Vì mã chỉ sử dụng data1, đó là những gì chúng tôi sẽ xem xét. Từ những gì tôi có thể biết, data1 chứa tất cả dữ liệu xung quanh một sự kiện quan trọng. Điều đó có nghĩa là nó chứa mã khóa và bất kỳ cờ chính nào. Có vẻ như các cờ chính chứa thông tin về trạng thái của khóa (Phím có bị ấn xuống không? Phím đã được giải phóng chưa?) Cũng như việc khóa có được giữ và lặp lại tín hiệu hay không. Tôi cũng đoán rằng mã khóa và cờ chính đều chiếm một nửa dữ liệu chứa trong data1 và các hoạt động bitwise tách dữ liệu đó ra thành các biến thích hợp. Sau khi chúng tôi nhận được các giá trị chúng tôi cần, chúng tôi gọi số mediaKeyEvent() mà tôi sẽ nhận được trong giây lát. Bất kể những sự kiện nào được gửi đến số MediaApplication của chúng tôi, chúng tôi cũng muốn mặc định NSApplication để xử lý tất cả các sự kiện. Để làm điều này, chúng tôi gọi super.sendEvent(event) ở cuối chức năng. Bây giờ, chúng ta hãy xem mediaKeyEvent().

func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) { 
    // Only send events on KeyDown. Without this check, these events will happen twice 
    if (state) { 
     switch(key) { 
     case NX_KEYTYPE_PLAY: 
      // Do work 
      break 
     case NX_KEYTYPE_FAST: 
      // Do work 
      break 
     case NX_KEYTYPE_REWIND: 
      // Do work 
      break 
     default: 
      break 
     } 
    } 
} 

Đây là nơi mọi thứ bắt đầu trở nên thú vị. Trước tiên, chúng tôi chỉ muốn kiểm tra xem phím nào đang được nhấn nếu state là đúng, trong trường hợp này là bất cứ khi nào phím được nhấn. Khi chúng tôi kiểm tra các khóa, chúng tôi tìm kiếm NX_KEYTYPE_PLAY, NX_KEYTYPE_FASTNX_KEYTYPE_REWIND. Nếu chức năng của chúng không rõ ràng, NX_KEYTYPE_PLAY là phím phát/tạm dừng, NX_KEYTYPE_FAST là khóa tiếp theo và NX_KEYTYPE_REWIND là khóa trước đó. Ngay bây giờ, không có gì xảy ra khi bất kỳ phím nào được nhấn xuống, vì vậy hãy đi qua một số logic có thể. Chúng ta sẽ bắt đầu với một kịch bản đơn giản.

case NX_KEYTYPE_PLAY: 
    print("Play") 
    break 

Với mã này khi ứng dụng phát hiện phím chơi/tạm dừng được nhấn, bạn sẽ thấy "Phát" được in ra bảng điều khiển. Đơn giản, phải không? Hãy lên ante bằng cách gọi các hàm trong ứng dụng của bạn là NSApplicationDelegate. Trước tiên, chúng tôi giả định rằng NSApplicationDelegate của bạn có chức năng được gọi là printMessage. Chúng tôi sẽ sửa đổi nó khi chúng tôi đi, vì vậy hãy chú ý đến những thay đổi. Chúng sẽ nhỏ, nhưng những thay đổi sẽ tác động đến cách bạn gọi chúng từ mediaEventKey.

func printMessage() { 
    print("Hello World") 
} 

Đây là trường hợp đơn giản nhất. Khi gọi printMessage(), bạn sẽ thấy "Hello World" trong bảng điều khiển của mình. Bạn có thể gọi điều này bằng cách gọi số performSelector trên số NSApplicationDelegate của mình, bạn có thể truy cập thông qua số MediaApplication. performSelector chụp trong một Selector chỉ là tên của hàm trong số NSApplicationDelegate của bạn.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage") 
    break 

Bây giờ, khi ứng dụng phát hiện phím chơi/tạm dừng đã được nhấn, bạn sẽ thấy "Hello World" được in trên bảng điều khiển. Hãy bắt đầu với một phiên bản mới của printMessage có tham số.

func printMessage(arg: String) { 
    print(arg) 
} 

Ý tưởng giờ đây là nếu bạn thấy "Hello World" trong bảng điều khiển của mình. Bây giờ chúng ta có thể sửa đổi cuộc gọi performSelector để xử lý truyền trong một tham số.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage:", withObject: "Hello World") 
    break 

Có một vài điều cần lưu ý về thay đổi này. Trước tiên, điều quan trọng cần lưu ý là : đã được thêm vào Selector. Điều này tách tên hàm từ tham số khi nó được gửi đến đại biểu. Cách hoạt động không quá quan trọng để ghi nhớ, nhưng đó là điều gì đó dọc theo các dòng của đại biểu gọi số printMessage:"Hello World". Tôi khá chắc chắn rằng không phải là chính xác 100% vì nó có khả năng sẽ sử dụng một ID đối tượng của một số loại, nhưng tôi đã không thực hiện bất kỳ đào sâu vào chi tiết cụ thể. Dù bằng cách nào, điều quan trọng cần nhớ là thêm : khi chuyển vào một tham số .. Điều thứ hai cần lưu ý là chúng tôi đã thêm tham số withObject. withObject mất AnyObject? làm giá trị. Trong trường hợp này, chúng tôi chỉ chuyển vào một số String vì đó là những gì printMessage đang tìm kiếm. Khi ứng dụng của bạn phát hiện rằng phím play/pause đã được nhấn, bạn vẫn sẽ thấy "Hello World" trong bảng điều khiển. Hãy xem xét một trường hợp sử dụng cuối cùng: một phiên bản của printMessage không chỉ có một mà là hai tham số.

func printMessage(arg: String, _ arg2: String) { 
    print(arg) 
} 

Bây giờ, nếu printMessage("Hello", "World") được gọi, bạn sẽ thấy "Hello World" trong bảng điều khiển của mình. Bây giờ chúng ta có thể sửa đổi cuộc gọi performSelector để xử lý truyền trong hai tham số.

case NX_KEYTYPE_PLAY: 
    delegate!.performSelector("printMessage::", withObject: "Hello", withObject: "World") 
    break 

Như trước đây, có hai điều cần lưu ý ở đây. Trước tiên, chúng tôi hiện thêm hai : vào cuối Selector. Giống như trước đây, điều này là để các đại biểu có thể vượt qua thông tin cùng có chứa các tham số. Ở một mức độ rất cơ bản, nó sẽ trông giống như printMessage:"Hello":"World", nhưng một lần nữa tôi không biết nó thực sự trông như thế nào ở một mức độ sâu hơn. Điều thứ hai cần lưu ý là chúng tôi đã thêm thông số withObject thứ hai vào cuộc gọi performSelector. Giống như trước đây, điều này withObject mất một AnyObject? làm giá trị và chúng tôi đang chuyển vào một số String vì đó là những gì printMessage muốn.Khi ứng dụng của bạn phát hiện rằng phím play/pause đã được nhấn, bạn vẫn sẽ thấy "Hello World" trong bảng điều khiển.

Điều cuối cùng cần lưu ý là performSelector chỉ có thể chấp nhận tối đa hai tham số. Tôi thực sự muốn thấy Swift thêm các khái niệm như splatting hoặc varargs để giới hạn này cuối cùng biến mất, nhưng bây giờ chỉ cần tránh cố gắng gọi các hàm đòi hỏi nhiều hơn hai tham số.

Đây là những gì một lớp học rất đơn giản MediaApplication mà chỉ in ra một số văn bản sẽ trông như thế nào khi bạn đang thực hiện với tất cả mọi thứ trên:

import Cocoa 

class MediaApplication: NSApplication { 
    override func sendEvent(event: NSEvent) { 
     if (event.type == .SystemDefined && event.subtype.rawValue == 8) { 
      let keyCode = ((event.data1 & 0xFFFF0000) >> 16) 
      let keyFlags = (event.data1 & 0x0000FFFF) 
      // Get the key state. 0xA is KeyDown, OxB is KeyUp 
      let keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA 
      let keyRepeat = (keyFlags & 0x1) 
      mediaKeyEvent(Int32(keyCode), state: keyState, keyRepeat: Bool(keyRepeat)) 
     } 

     super.sendEvent(event) 
    } 

    func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) { 
     // Only send events on KeyDown. Without this check, these events will happen twice 
     if (state) { 
      switch(key) { 
      case NX_KEYTYPE_PLAY: 
       print("Play") 
       break 
      case NX_KEYTYPE_FAST: 
       print("Next") 
       break 
      case NX_KEYTYPE_REWIND: 
       print("Prev") 
       break 
      default: 
       break 
      } 
     } 
    } 
} 

Bây giờ, tôi cũng cần nói thêm rằng, theo mặc định, ứng dụng của bạn là sẽ sử dụng tiêu chuẩn NSApplication khi nó chạy. Nếu bạn muốn sử dụng MediaApplication toàn bộ bài đăng này, bạn sẽ cần phải tiếp tục và sửa đổi tệp Info.plist của ứng dụng của bạn. Nếu bạn đang trong giao diện đồ họa, nó sẽ giống như thế này:

Info.plist

Nếu không, nó sẽ giống như thế này:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 
<plist version="1.0"> 
<dict> 
    <key>CFBundleDevelopmentRegion</key> 
    <string>en</string> 
    <key>CFBundleExecutable</key> 
    <string>$(EXECUTABLE_NAME)</string> 
    <key>CFBundleIconFile</key> 
    <string></string> 
    <key>CFBundleIdentifier</key> 
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> 
    <key>CFBundleInfoDictionaryVersion</key> 
    <string>6.0</string> 
    <key>CFBundleName</key> 
    <string>$(PRODUCT_NAME)</string> 
    <key>CFBundlePackageType</key> 
    <string>APPL</string> 
    <key>CFBundleShortVersionString</key> 
    <string>1.0</string> 
    <key>CFBundleSignature</key> 
    <string>????</string> 
    <key>CFBundleVersion</key> 
    <string>1</string> 
    <key>LSApplicationCategoryType</key> 
    <string>public.app-category.utilities</string> 
    <key>LSMinimumSystemVersion</key> 
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string> 
    <key>LSUIElement</key> 
    <true/> 
    <key>NSHumanReadableCopyright</key> 
    <string>Copyright © 2015 Chris Rees. All rights reserved.</string> 
    <key>NSMainNibFile</key> 
    <string>MainMenu</string> 
    <key>NSPrincipalClass</key> 
    <string>NSApplication</string> 
</dict> 
</plist> 

Trong cả hai trường hợp, bạn sẽ muốn thay đổi thuộc tính NSPrincipalClass. Giá trị mới sẽ bao gồm tên dự án của bạn, vì vậy nó sẽ là một cái gì đó như Notify.MediaApplication. Khi bạn thực hiện thay đổi, hãy chạy ứng dụng của bạn và sử dụng các khóa phương tiện đó!

+0

hey @Serneum cảm ơn các chi tiết đẹp. Đây là vấn đề của tôi: chúng tôi có thể chặn sự kiện này cho iTunes không? thực sự bất cứ khi nào bấm phím bấm nó sẽ khởi động iTunes. – AJit

+0

Để xử lý iTunes khởi chạy trên bấm phím phương tiện. https://github.com/nevyn/SPMediaKeyTap – AJit

+0

Giải pháp hay nhưng không hoạt động khi bạn tạo ứng dụng theo chương trình (sử dụng main.swift thay vì sử dụng bảng phân cảnh hoặc tệp xib) – ErwinGO

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