我希望我的应用程序响应F7,F8和F9键盘媒体控制按钮。
我知道这个可爱的库,但无法与Swift结合使用:https://github.com/nevyn/SPMediaKeyTap
最佳答案
我实际上是前几天自己解决了这个问题。我在上面写了一个blog post和一个Gist
我将嵌入博客文章和最终代码,以防博客或Gist消失。 注意:这是一篇很长的文章,详细介绍了如何构造类以及如何在应用程序的委托(delegate)中调用其他方法。如果您只需要完成的产品(MediaApplication类),请移至底部。它位于XML和Info.plist信息上方。
对于初学者,要从媒体密钥获取密钥事件,您需要创建一个扩展NSApplication
的类。这很简单
import Cocoa
class MediaApplication: NSApplication {
}
接下来,我们需要覆盖
sendEvent()
函数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)
}
现在,我不假装完全理解这里发生的事情,但是我认为我有一个不错的主意。
NSEvent
对象包含几个关键属性:type
,subtype
,data1
和data2
。 Type
和subtype
相当不言自明,但是data1
和data2
非常模糊。由于该代码仅使用data1
,因此我们将要研究它。据我所知,data1
包含键事件周围的所有数据。这意味着它包含键代码和所有键标志。似乎按键标志包含有关按键状态(按键是否被按下?按键是否被释放?)以及按键是否被按下并重复信号的信息。我还猜测键代码和键标志都占用了data1
中包含的数据的一半,而按位操作将这些数据分离为适当的变量。在获得所需的值之后,我们将调用mediaKeyEvent()
,稍后我将介绍它。无论将什么事件发送到我们的MediaApplication
,我们都希望默认的NSApplication
也能够处理所有事件。为此,我们在函数末尾调用super.sendEvent(event)
。现在,让我们看一下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
}
}
}
这是事情开始变得有趣的地方。首先,我们只想检查
state
为true时正在按下的键,在这种情况下,只要按下该键即可。一旦检查了密钥,我们将查找NX_KEYTYPE_PLAY
,NX_KEYTYPE_FAST
和NX_KEYTYPE_REWIND
。如果它们的功能不明显,则NX_KEYTYPE_PLAY
是播放/暂停键,NX_KEYTYPE_FAST
是下一个键,NX_KEYTYPE_REWIND
是上一个键。现在,当按下这些键中的任何一个时都什么也没有发生,因此让我们回顾一些可能的逻辑。我们将从一个简单的场景开始。case NX_KEYTYPE_PLAY:
print("Play")
break
使用此代码后,当您的应用程序检测到已按下“播放/暂停”键时,您会看到“播放”已打印到控制台。简单吧?让我们通过调用应用程序的
NSApplicationDelegate
中的函数来提高效率。首先,我们假设您的NSApplicationDelegate
具有一个名为printMessage
的函数。我们将不断进行修改,因此请密切注意所做的更改。它们很小,但是更改将影响您从mediaEventKey
调用它们的方式。func printMessage() {
print("Hello World")
}
这是最简单的情况。调用
printMessage()
时,您将在控制台中看到“Hello World”。您可以通过在performSelector
上访问NSApplicationDelegate
上的MediaApplication
来调用它。 performSelector
接受Selector
,这只是NSApplicationDelegate
中函数的名称。case NX_KEYTYPE_PLAY:
delegate!.performSelector("printMessage")
break
现在,当您的应用程序检测到已按下“播放/暂停”键时,将在控制台上看到“Hello World”。让我们用一个带有参数的新版本
printMessage
来提高层次。func printMessage(arg: String) {
print(arg)
}
现在的想法是,如果调用
printMessage("Hello World")
,您将在控制台中看到“Hello World”。现在,我们可以修改performSelector
调用以处理传入参数。case NX_KEYTYPE_PLAY:
delegate!.performSelector("printMessage:", withObject: "Hello World")
break
关于此更改,需要注意几件事。首先,注意添加到
:
中的Selector
很重要。当函数名称发送给委托(delegate)时,它将函数名称与参数分开。记住它的工作方式并不是很重要,但这与调用printMessage:"Hello World"
的委托(delegate)相似。我相当确定这不是100%正确的,因为它可能会使用某种对象ID,但是我没有做任何深入的研究。无论哪种方式,要记住的重要事情是在传递参数时添加:
。要注意的第二件事是我们添加了withObject
参数。 withObject
以AnyObject?
作为值。在这种情况下,我们只是传入String
,因为这就是printMessage
所要查找的。当您的应用程序检测到已按下播放/暂停键时,您仍应在控制台中看到“Hello World”。让我们看一个最终的用例:printMessage
的一个版本,它不接受一个参数,而是两个参数。func printMessage(arg: String, _ arg2: String) {
print(arg)
}
现在,如果调用
printMessage("Hello", "World")
,您将在控制台中看到“Hello World”。现在,我们可以修改performSelector
调用以处理传入的两个参数。case NX_KEYTYPE_PLAY:
delegate!.performSelector("printMessage::", withObject: "Hello", withObject: "World")
break
和以前一样,这里有两件事需要注意。首先,我们现在在
:
的末尾添加两个Selector
。像以前一样,这样做是为了使委托(delegate)可以传递包含参数的信息。从最基本的 Angular 来看,它看起来像printMessage:"Hello":"World"
,但我仍然不知道它在更深层次上的真正样子。注意的第二件事是,我们在withObject
调用中添加了第二个performSelector
参数。像以前一样,此withObject
将AnyObject?
作为值,并且我们传递了String
,因为这就是printMessage
想要的。当您的应用程序检测到已按下播放/暂停键时,您仍应在控制台中看到“Hello World”。最后要注意的一件事是
performSelector
最多只能接受两个参数。我真的很想看到Swift添加诸如splatting或varargs之类的概念,以便最终消除此限制,但是现在就避免尝试调用需要两个以上参数的函数。完成以上所有操作后,这就是一个非常简单的
MediaApplication
类,它只打印出一些文本: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
}
}
}
}
现在,我还应该补充一点,默认情况下,您的应用程序将在运行时使用标准
NSApplication
。如果您想使用整个文章所涉及的MediaApplication
,则需要继续修改应用程序的Info.plist
文件。如果您在图形 View 中,它将看起来像这样:(来源:sernprogramming.com)
否则,它将看起来像这样:
<?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>
无论哪种情况,您都需要更改
NSPrincipalClass
属性。新值将包含您的项目名称,因此将类似于Notify.MediaApplication
。进行更改后,运行您的应用程序并使用这些媒体密钥!
关于macos - 在Swift中捕获OSX媒体控制按钮,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32499676/