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
, data1
và data2
. Type
và subtype
khá tự giải thích, nhưng data1
và data2
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_FAST
và NX_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:
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 đó!
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
Để xử lý iTunes khởi chạy trên bấm phím phương tiện. https://github.com/nevyn/SPMediaKeyTap – AJit
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