diff --git a/.gitignore b/.gitignore index cc76ed2..5079e38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.build/ build/ DerivedData/ diff --git a/.swift-version b/.swift-version index 5186d07..a75b92f 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 +5.1 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..e9c0c81 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,5 @@ +--indent 2 +--ranges no-space +--self insert +--exponentcase lowercase +--exclude Carthage diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 index 955032d..bd6db31 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 2.2.1 - 2019-02-24 +- Avoid TIS/TSM error due to creation of NSEvents on a background thread. + +# 2.2.0 - 2018-10-27 +- Mark callbacks as @escaping after https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md + # 2.1.0 - 2017-12-24 - Add manual activation function to allow for the current application to be activated without receiving an NSNotification. diff --git a/MediaKeyTap.podspec b/MediaKeyTap.podspec old mode 100644 new mode 100755 index 3e51b62..651e4ac --- a/MediaKeyTap.podspec +++ b/MediaKeyTap.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = "MediaKeyTap" - s.version = "2.1.0" + s.version = "3.1.0" s.summary = "Access the Mac's media keys in Swift" s.homepage = "https://github.com/nhurden/MediaKeyTap" s.license = { type: 'MIT', file: 'LICENSE' } s.author = { "Nicholas Hurden" => "git@nhurden.com" } - s.source = { git: "https://github.com/nhurden/MediaKeyTap.git", tag: s.version.to_s } + s.source = { git: "https://github.com/the0neyouseek/MediaKeyTap.git", tag: s.version.to_s } s.platform = :osx, '10.10' s.requires_arc = true diff --git a/MediaKeyTap.xcodeproj/project.pbxproj b/MediaKeyTap.xcodeproj/project.pbxproj index ef49ea9..41b757d 100644 --- a/MediaKeyTap.xcodeproj/project.pbxproj +++ b/MediaKeyTap.xcodeproj/project.pbxproj @@ -8,14 +8,10 @@ /* Begin PBXBuildFile section */ E11463B21D9BAE8400A854EE /* NSEventExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11463B11D9BAE8400A854EE /* NSEventExtensions.swift */; }; - E11463B51D9BB34900A854EE /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E11463B41D9BB34900A854EE /* CHANGELOG.md */; }; E18A87D71C7A200900825AE3 /* MediaKeyTap.h in Headers */ = {isa = PBXBuildFile; fileRef = E18A87D61C7A200900825AE3 /* MediaKeyTap.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E18A87E21C7A204200825AE3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A87DE1C7A204200825AE3 /* Extensions.swift */; }; E18A87E31C7A204200825AE3 /* MediaApplicationWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A87DF1C7A204200825AE3 /* MediaApplicationWatcher.swift */; }; E18A87E41C7A204200825AE3 /* MediaKeyTap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A87E01C7A204200825AE3 /* MediaKeyTap.swift */; }; E18A87E51C7A204200825AE3 /* MediaKeyTapInternals.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A87E11C7A204200825AE3 /* MediaKeyTapInternals.swift */; }; - E18A880C1C7A25B000825AE3 /* MediaKeyTap.podspec in Resources */ = {isa = PBXBuildFile; fileRef = E18A88091C7A25B000825AE3 /* MediaKeyTap.podspec */; }; - E18A880E1C7A25B000825AE3 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = E18A880B1C7A25B000825AE3 /* LICENSE */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -30,18 +26,13 @@ /* Begin PBXFileReference section */ E11463B11D9BAE8400A854EE /* NSEventExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSEventExtensions.swift; sourceTree = ""; }; - E11463B41D9BB34900A854EE /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; E18A87D31C7A200900825AE3 /* MediaKeyTap.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MediaKeyTap.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E18A87D61C7A200900825AE3 /* MediaKeyTap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaKeyTap.h; sourceTree = ""; }; E18A87D81C7A200900825AE3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E18A87DE1C7A204200825AE3 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; E18A87DF1C7A204200825AE3 /* MediaApplicationWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaApplicationWatcher.swift; sourceTree = ""; }; E18A87E01C7A204200825AE3 /* MediaKeyTap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaKeyTap.swift; sourceTree = ""; }; E18A87E11C7A204200825AE3 /* MediaKeyTapInternals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaKeyTapInternals.swift; sourceTree = ""; }; E18A88011C7A237B00825AE3 /* MediaKeyTapExample.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MediaKeyTapExample.xcodeproj; path = MediaKeyTapExample/MediaKeyTapExample.xcodeproj; sourceTree = ""; }; - E18A88091C7A25B000825AE3 /* MediaKeyTap.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MediaKeyTap.podspec; sourceTree = ""; }; - E18A880A1C7A25B000825AE3 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - E18A880B1C7A25B000825AE3 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -58,13 +49,9 @@ E18A87C91C7A200900825AE3 = { isa = PBXGroup; children = ( - E18A88011C7A237B00825AE3 /* MediaKeyTapExample.xcodeproj */, E18A87D51C7A200900825AE3 /* MediaKeyTap */, + E18A88011C7A237B00825AE3 /* MediaKeyTapExample.xcodeproj */, E18A87D41C7A200900825AE3 /* Products */, - E18A88091C7A25B000825AE3 /* MediaKeyTap.podspec */, - E18A880A1C7A25B000825AE3 /* README.md */, - E18A880B1C7A25B000825AE3 /* LICENSE */, - E11463B41D9BB34900A854EE /* CHANGELOG.md */, ); sourceTree = ""; }; @@ -79,13 +66,12 @@ E18A87D51C7A200900825AE3 /* MediaKeyTap */ = { isa = PBXGroup; children = ( - E18A87E01C7A204200825AE3 /* MediaKeyTap.swift */, + E18A87D81C7A200900825AE3 /* Info.plist */, E18A87DF1C7A204200825AE3 /* MediaApplicationWatcher.swift */, + E18A87D61C7A200900825AE3 /* MediaKeyTap.h */, + E18A87E01C7A204200825AE3 /* MediaKeyTap.swift */, E18A87E11C7A204200825AE3 /* MediaKeyTapInternals.swift */, E11463B11D9BAE8400A854EE /* NSEventExtensions.swift */, - E18A87DE1C7A204200825AE3 /* Extensions.swift */, - E18A87D61C7A200900825AE3 /* MediaKeyTap.h */, - E18A87D81C7A200900825AE3 /* Info.plist */, ); path = MediaKeyTap; sourceTree = ""; @@ -136,18 +122,18 @@ E18A87CA1C7A200900825AE3 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = "Nicholas Hurden"; TargetAttributes = { E18A87D21C7A200900825AE3 = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0910; + LastSwiftMigration = 1020; }; }; }; buildConfigurationList = E18A87CD1C7A200900825AE3 /* Build configuration list for PBXProject "MediaKeyTap" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -183,9 +169,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - E18A880E1C7A25B000825AE3 /* LICENSE in Resources */, - E18A880C1C7A25B000825AE3 /* MediaKeyTap.podspec in Resources */, - E11463B51D9BB34900A854EE /* CHANGELOG.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -196,11 +179,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E11463B21D9BAE8400A854EE /* NSEventExtensions.swift in Sources */, - E18A87E41C7A204200825AE3 /* MediaKeyTap.swift in Sources */, E18A87E31C7A204200825AE3 /* MediaApplicationWatcher.swift in Sources */, + E18A87E41C7A204200825AE3 /* MediaKeyTap.swift in Sources */, E18A87E51C7A204200825AE3 /* MediaKeyTapInternals.swift in Sources */, - E18A87E21C7A204200825AE3 /* Extensions.swift in Sources */, + E11463B21D9BAE8400A854EE /* NSEventExtensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -219,12 +201,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -274,12 +258,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -327,8 +313,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -348,8 +333,7 @@ PRODUCT_BUNDLE_IDENTIFIER = nhurden.MediaKeyTap; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/MediaKeyTap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MediaKeyTap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/MediaKeyTap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/MediaKeyTap.xcodeproj/xcshareddata/xcschemes/MediaKeyTap.xcscheme b/MediaKeyTap.xcodeproj/xcshareddata/xcschemes/MediaKeyTap.xcscheme index 7b286dd..842074f 100644 --- a/MediaKeyTap.xcodeproj/xcshareddata/xcschemes/MediaKeyTap.xcscheme +++ b/MediaKeyTap.xcodeproj/xcshareddata/xcschemes/MediaKeyTap.xcscheme @@ -1,6 +1,6 @@ @@ -37,7 +36,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/MediaKeyTap/Extensions.swift b/MediaKeyTap/Extensions.swift deleted file mode 100644 index 8bb9a8b..0000000 --- a/MediaKeyTap/Extensions.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Extensions.swift -// Castle -// -// Created by Nicholas Hurden on 22/02/2016. -// Copyright © 2016 Nicholas Hurden. All rights reserved. -// - -import Foundation - -precedencegroup FunctorPrecedence { - associativity: left - higherThan: DefaultPrecedence -} - -infix operator <^> : FunctorPrecedence - -func <^>(f: (T) -> U, ap: T?) -> U? { - return ap.map(f) -} diff --git a/MediaKeyTap/MediaApplicationWatcher.swift b/MediaKeyTap/MediaApplicationWatcher.swift index d006b47..4d10817 100644 --- a/MediaKeyTap/MediaApplicationWatcher.swift +++ b/MediaKeyTap/MediaApplicationWatcher.swift @@ -10,14 +10,14 @@ import Cocoa -protocol MediaApplicationWatcherDelegate { +protocol MediaApplicationWatcherDelegate: class { func updateIsActiveMediaApp(_ active: Bool) func whitelistedAppStarted() } class MediaApplicationWatcher { var mediaApps: [NSRunningApplication] - var delegate: MediaApplicationWatcherDelegate? + weak var delegate: MediaApplicationWatcherDelegate? // A set of bundle identifiers that notifications have been received from var dynamicWhitelist: Set @@ -26,12 +26,8 @@ class MediaApplicationWatcher { let mediaKeyTapReplyNotification = "MediaKeyTapReply" // Sent on receipt of a mediaKeyTapDidStartNotification init() { - self.mediaApps = [] - self.dynamicWhitelist = [] - } - - deinit { - stop() + mediaApps = [] + dynamicWhitelist = [] } /// Activate the currently running application (without an NSNotification) @@ -42,20 +38,26 @@ class MediaApplicationWatcher { func start() { let notificationCenter = NSWorkspace.shared.notificationCenter - notificationCenter.addObserver(self, - selector: #selector(applicationLaunched), - name: NSWorkspace.didLaunchApplicationNotification, - object: nil) - - notificationCenter.addObserver(self, - selector: #selector(applicationActivated), - name: NSWorkspace.didActivateApplicationNotification, - object: nil) - - notificationCenter.addObserver(self, - selector: #selector(applicationTerminated), - name: NSWorkspace.didTerminateApplicationNotification, - object: nil) + notificationCenter.addObserver( + self, + selector: #selector(applicationLaunched), + name: NSWorkspace.didLaunchApplicationNotification, + object: nil + ) + + notificationCenter.addObserver( + self, + selector: #selector(applicationActivated), + name: NSWorkspace.didActivateApplicationNotification, + object: nil + ) + + notificationCenter.addObserver( + self, + selector: #selector(applicationTerminated), + name: NSWorkspace.didTerminateApplicationNotification, + object: nil + ) setupDistributedNotifications() } @@ -72,19 +74,37 @@ class MediaApplicationWatcher { // media tap immediately when new media apps are launched let ownBundleIdentifier = Bundle.main.bundleIdentifier - distributedNotificationCenter.postNotificationName(NSNotification.Name(rawValue: mediaKeyTapDidStartNotification), object: ownBundleIdentifier, userInfo: nil, deliverImmediately: true) - - distributedNotificationCenter.addObserver(forName: NSNotification.Name(rawValue: mediaKeyTapDidStartNotification), object: nil, queue: nil) { notification in + distributedNotificationCenter.postNotificationName( + NSNotification.Name(rawValue: mediaKeyTapDidStartNotification), + object: ownBundleIdentifier, + userInfo: nil, + deliverImmediately: true + ) + + distributedNotificationCenter.addObserver( + forName: NSNotification.Name(rawValue: mediaKeyTapDidStartNotification), + object: nil, + queue: nil + ) { notification in if let otherBundleIdentifier = notification.object as? String { guard otherBundleIdentifier != ownBundleIdentifier else { return } self.dynamicWhitelist.insert(otherBundleIdentifier) // Send a reply so that the sender knows that this app exists - distributedNotificationCenter.postNotificationName(NSNotification.Name(rawValue: self.mediaKeyTapReplyNotification), object: ownBundleIdentifier, userInfo: nil, deliverImmediately: true) + distributedNotificationCenter.postNotificationName( + NSNotification.Name(rawValue: self.mediaKeyTapReplyNotification), + object: ownBundleIdentifier, + userInfo: nil, + deliverImmediately: true + ) } } - distributedNotificationCenter.addObserver(forName: NSNotification.Name(rawValue: mediaKeyTapReplyNotification), object: nil, queue: nil) { notification in + distributedNotificationCenter.addObserver( + forName: NSNotification.Name(rawValue: mediaKeyTapReplyNotification), + object: nil, + queue: nil + ) { notification in if let otherBundleIdentifier = notification.object as? String { guard otherBundleIdentifier != ownBundleIdentifier else { return } self.dynamicWhitelist.insert(otherBundleIdentifier) @@ -96,7 +116,7 @@ class MediaApplicationWatcher { @objc private func applicationLaunched(_ notification: Notification) { if let application = (notification as NSNotification).userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { - if inStaticWhitelist(application) && application != NSRunningApplication.current { + if inStaticWhitelist(application), application != NSRunningApplication.current { delegate?.whitelistedAppStarted() } } @@ -105,6 +125,7 @@ class MediaApplicationWatcher { @objc private func applicationActivated(_ notification: Notification) { if let application = (notification as NSNotification).userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication { guard whitelisted(application) else { return } + handleApplicationActivation(application: application) } } @@ -168,7 +189,7 @@ class MediaApplicationWatcher { "org.niltsh.MPlayerX", "org.quodlibet.quodlibet", "org.videolan.vlc", - "ru.ya.themblsha.YandexMusic" + "ru.ya.themblsha.YandexMusic", ] if let ownIdentifier = Bundle.main.bundleIdentifier { @@ -179,14 +200,16 @@ class MediaApplicationWatcher { } private func inStaticWhitelist(_ application: NSRunningApplication) -> Bool { - return (whitelistedApplicationIdentifiers().contains <^> application.bundleIdentifier) ?? false + guard let id = application.bundleIdentifier else { return false } + return whitelistedApplicationIdentifiers().contains(id) } private func inDynamicWhitelist(_ application: NSRunningApplication) -> Bool { - return (dynamicWhitelist.contains <^> application.bundleIdentifier) ?? false + guard let id = application.bundleIdentifier else { return false } + return dynamicWhitelist.contains(id) } private func whitelisted(_ application: NSRunningApplication) -> Bool { - return inStaticWhitelist(application) || inDynamicWhitelist(application) + inStaticWhitelist(application) || inDynamicWhitelist(application) } } diff --git a/MediaKeyTap/MediaKeyTap.swift b/MediaKeyTap/MediaKeyTap.swift index 31a2ce8..794835a 100644 --- a/MediaKeyTap/MediaKeyTap.swift +++ b/MediaKeyTap/MediaKeyTap.swift @@ -14,6 +14,11 @@ public enum MediaKey { case next case rewind case fastForward + case brightnessUp + case brightnessDown + case volumeUp + case volumeDown + case mute } public enum KeyPressMode { @@ -28,36 +33,54 @@ public typealias KeyFlags = Int32 public struct KeyEvent { public let keycode: Keycode public let keyFlags: KeyFlags - public let keyPressed: Bool // Will be true after a keyDown and false after a keyUp + public let keyPressed: Bool // Will be true after a keyDown and false after a keyUp public let keyRepeat: Bool } -public protocol MediaKeyTapDelegate { - func handle(mediaKey: MediaKey, event: KeyEvent) +public protocol MediaKeyTapDelegate: class { + func handle(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) } public class MediaKeyTap { - let delegate: MediaKeyTapDelegate + public static var useAlternateBrightnessKeys: Bool = true + weak var delegate: MediaKeyTapDelegate! let mediaApplicationWatcher: MediaApplicationWatcher let internals: MediaKeyTapInternals let keyPressMode: KeyPressMode + var observeBuiltIn: Bool = true + var keysToWatch: [MediaKey] = [ + .playPause, + .previous, + .next, + .rewind, + .fastForward, + .brightnessUp, + .brightnessDown, + .volumeUp, + .volumeDown, + .mute, + ] var interceptMediaKeys: Bool { didSet { if interceptMediaKeys != oldValue { - self.internals.enableTap(interceptMediaKeys) + internals.enableTap(interceptMediaKeys) } } } // MARK: - Setup - public init(delegate: MediaKeyTapDelegate, on mode: KeyPressMode = .keyDown) { + public init(delegate: MediaKeyTapDelegate, on mode: KeyPressMode = .keyDown, for keys: [MediaKey] = [], observeBuiltIn: Bool = true) { self.delegate = delegate - self.interceptMediaKeys = false - self.mediaApplicationWatcher = MediaApplicationWatcher() - self.internals = MediaKeyTapInternals() - self.keyPressMode = mode + interceptMediaKeys = false + mediaApplicationWatcher = MediaApplicationWatcher() + internals = MediaKeyTapInternals() + keyPressMode = mode + self.observeBuiltIn = observeBuiltIn + if keys.count > 0 { + keysToWatch = keys + } } /// Activate the currently running application @@ -79,13 +102,37 @@ public class MediaKeyTap { } catch {} } - private func keycodeToMediaKey(_ keycode: Keycode) -> MediaKey? { + /// Stop the key tap + open func stop() { + mediaApplicationWatcher.stop() + internals.stopWatchingMediaKeys() + + mediaApplicationWatcher.delegate = nil + internals.delegate = nil + } + + public static func keycodeToMediaKey(_ keycode: Keycode) -> MediaKey? { switch keycode { case NX_KEYTYPE_PLAY: return .playPause case NX_KEYTYPE_PREVIOUS: return .previous case NX_KEYTYPE_NEXT: return .next case NX_KEYTYPE_REWIND: return .rewind case NX_KEYTYPE_FAST: return .fastForward + case NX_KEYTYPE_BRIGHTNESS_UP: return .brightnessUp + case NX_KEYTYPE_BRIGHTNESS_DOWN: return .brightnessDown + case NX_KEYTYPE_SOUND_UP: return .volumeUp + case NX_KEYTYPE_SOUND_DOWN: return .volumeDown + case NX_KEYTYPE_MUTE: return .mute + default: return nil + } + } + + public static func functionKeyCodeToMediaKey(_ keycode: Keycode) -> MediaKey? { + switch keycode { + case 107: return (useAlternateBrightnessKeys ? .brightnessDown : nil) // F14 + case 113: return (useAlternateBrightnessKeys ? .brightnessUp : nil) // F15 + case 144: return .brightnessUp // Brightness up media key + case 145: return .brightnessDown // Brightness down media key default: return nil } } @@ -104,7 +151,10 @@ public class MediaKeyTap { extension MediaKeyTap: MediaApplicationWatcherDelegate { func updateIsActiveMediaApp(_ active: Bool) { - interceptMediaKeys = active + let keysMedia: [MediaKey] = [.playPause, .previous, .next, .rewind, .fastForward] + if Set(keysToWatch).intersection(Set(keysMedia)).count > 0 { + interceptMediaKeys = active + } } // When a static whitelisted app starts, we need to restart the tap to ensure that @@ -124,15 +174,17 @@ extension MediaKeyTap: MediaKeyTapInternalsDelegate { interceptMediaKeys = intercept } - func handle(keyEvent: KeyEvent) { - if let key = keycodeToMediaKey(keyEvent.keycode) { + func handle(keyEvent: KeyEvent, isFunctionKey: Bool, modifiers: NSEvent.ModifierFlags?) { + if let key = isFunctionKey ? MediaKeyTap.functionKeyCodeToMediaKey(keyEvent.keycode) : MediaKeyTap + .keycodeToMediaKey(keyEvent.keycode) + { if shouldNotifyDelegate(ofEvent: keyEvent) { - delegate.handle(mediaKey: key, event: keyEvent) + delegate.handle(mediaKey: key, event: keyEvent, modifiers: modifiers) } } } func isInterceptingMediaKeys() -> Bool { - return interceptMediaKeys + interceptMediaKeys } } diff --git a/MediaKeyTap/MediaKeyTapInternals.swift b/MediaKeyTap/MediaKeyTapInternals.swift index 8a9f08b..88ac99d 100644 --- a/MediaKeyTap/MediaKeyTapInternals.swift +++ b/MediaKeyTap/MediaKeyTapInternals.swift @@ -25,16 +25,26 @@ extension EventTapError: CustomStringConvertible { } } -protocol MediaKeyTapInternalsDelegate { +func mainScreen() -> NSScreen? { + let mouseLocation = NSEvent.mouseLocation + let screens = NSScreen.screens + let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) }) + + return screenWithMouse +} + +protocol MediaKeyTapInternalsDelegate: class { + var keysToWatch: [MediaKey] { get set } + var observeBuiltIn: Bool { get set } func updateInterceptMediaKeys(_ intercept: Bool) - func handle(keyEvent: KeyEvent) + func handle(keyEvent: KeyEvent, isFunctionKey: Bool, modifiers: NSEvent.ModifierFlags?) func isInterceptingMediaKeys() -> Bool } class MediaKeyTapInternals { typealias EventTapCallback = @convention(block) (CGEventType, CGEvent) -> CGEvent? - var delegate: MediaKeyTapInternalsDelegate? + weak var delegate: MediaKeyTapInternalsDelegate? var keyEventPort: CFMachPort? var runLoopSource: CFRunLoopSource? var callback: EventTapCallback? @@ -46,11 +56,12 @@ class MediaKeyTapInternals { } /** - Enable/Disable the underlying tap - */ + Enable/Disable the underlying tap + */ func enableTap(_ onOff: Bool) { - if let port = self.keyEventPort, let runLoop = self.runLoop { - CFRunLoopPerformBlock(runLoop, CFRunLoopMode.commonModes as CFTypeRef!) { + if keyEventPort != nil, let runLoop = self.runLoop { + CFRunLoopPerformBlock(runLoop, CFRunLoopMode.commonModes as CFTypeRef) { [weak self] in + guard let port = self?.keyEventPort else { return } CGEvent.tapEnable(tap: port, enable: onOff) } CFRunLoopWakeUp(runLoop) @@ -58,15 +69,16 @@ class MediaKeyTapInternals { } /** - Restart the tap, placing it in front of existing taps - */ + Restart the tap, placing it in front of existing taps + */ func restartTap() throws { stopWatchingMediaKeys() try startWatchingMediaKeys(restart: true) } func startWatchingMediaKeys(restart: Bool = false) throws { - let eventTapCallback: EventTapCallback = { type, event in + let eventTapCallback: EventTapCallback = { [weak self] type, event in + guard let self = self else { return event } if type == .tapDisabledByTimeout { if let port = self.keyEventPort { CGEvent.tapEnable(tap: port, enable: true) @@ -76,7 +88,9 @@ class MediaKeyTapInternals { return event } - return self.handle(event: event, ofType: type) + return DispatchQueue.main.sync { + self.handle(event: event, ofType: type) + } } try startKeyEventTap(callback: eventTapCallback, restart: restart) @@ -84,21 +98,58 @@ class MediaKeyTapInternals { } func stopWatchingMediaKeys() { - CFRunLoopSourceInvalidate <^> runLoopSource - CFRunLoopStop <^> runLoop - CFMachPortInvalidate <^> keyEventPort + if let runLoopSource = self.runLoopSource { + CFRunLoopSourceInvalidate(runLoopSource) + } + if let runLoop = self.runLoop { + CFRunLoopStop(runLoop) + } + if let keyEventPort = self.keyEventPort { + CFMachPortInvalidate(keyEventPort) + } } private func handle(event: CGEvent, ofType type: CGEventType) -> CGEvent? { + if type == .keyDown { + let keycode: Int64 = event.getIntegerValueField(.keyboardEventKeycode) + guard let mediaKey = MediaKeyTap.functionKeyCodeToMediaKey(Int32(keycode)) else { return event } + if delegate?.keysToWatch.contains(mediaKey) ?? false { + if !(delegate?.observeBuiltIn ?? true) { + if let id = mainScreen()?.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID { + if CGDisplayIsBuiltin(id) != 0 { + return event + } + } + } + + delegate?.handle( + keyEvent: KeyEvent(keycode: Int32(keycode), keyFlags: 0, keyPressed: true, keyRepeat: false), + isFunctionKey: true, + modifiers: NSEvent(cgEvent: event)?.modifierFlags + ) + + return nil + } else { + return event + } + } + if let nsEvent = NSEvent(cgEvent: event) { - guard type.rawValue == UInt32(NX_SYSDEFINED) - && nsEvent.isMediaKeyEvent - && delegate?.isInterceptingMediaKeys() ?? false + guard let mediaKey = MediaKeyTap.keycodeToMediaKey(nsEvent.keyEvent.keycode) else { return event } + guard type.rawValue == UInt32(NX_SYSDEFINED), + nsEvent.isMediaKeyEvent, + delegate?.keysToWatch.contains(mediaKey) ?? false, + delegate?.isInterceptingMediaKeys() ?? false else { return event } - DispatchQueue.main.async { - self.delegate?.handle(keyEvent: nsEvent.keyEvent) + if delegate?.observeBuiltIn ?? true == false { + if let id = mainScreen()?.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID { + if CGDisplayIsBuiltin(id) != 0 { + return event + } + } } + delegate?.handle(keyEvent: nsEvent.keyEvent, isFunctionKey: false, modifiers: nsEvent.modifierFlags) return nil } @@ -106,7 +157,7 @@ class MediaKeyTapInternals { return event } - private func startKeyEventTap(callback: EventTapCallback, restart: Bool) throws { + private func startKeyEventTap(callback: @escaping EventTapCallback, restart: Bool) throws { // On a restart we don't want to interfere with the application watcher if !restart { delegate?.updateInterceptMediaKeys(true) @@ -119,17 +170,18 @@ class MediaKeyTapInternals { guard let source = runLoopSource else { throw EventTapError.runLoopSourceCreationFailure } let queue = DispatchQueue(label: "MediaKeyTap Runloop", attributes: []) - self.runLoopQueue = queue + runLoopQueue = queue - queue.async { + queue.async { [weak self] in + guard let self = self else { return } self.runLoop = CFRunLoopGetCurrent() CFRunLoopAddSource(self.runLoop, source, CFRunLoopMode.commonModes) CFRunLoopRun() } } - private func keyCaptureEventTapPort(callback: EventTapCallback) -> CFMachPort? { - let cCallback: CGEventTapCallBack = { proxy, type, event, refcon in + private func keyCaptureEventTapPort(callback: @escaping EventTapCallback) -> CFMachPort? { + let cCallback: CGEventTapCallBack = { _, type, event, refcon in let innerBlock = unsafeBitCast(refcon, to: EventTapCallback.self) return innerBlock(type, event).map(Unmanaged.passUnretained) } @@ -140,8 +192,9 @@ class MediaKeyTapInternals { tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, - eventsOfInterest: CGEventMask(1 << NX_SYSDEFINED), + eventsOfInterest: CGEventMask(1 << NX_KEYDOWN) | CGEventMask(1 << NX_SYSDEFINED), callback: cCallback, - userInfo: refcon) + userInfo: refcon + ) } } diff --git a/MediaKeyTap/NSEventExtensions.swift b/MediaKeyTap/NSEventExtensions.swift index f730497..3e85bb6 100644 --- a/MediaKeyTap/NSEventExtensions.swift +++ b/MediaKeyTap/NSEventExtensions.swift @@ -6,27 +6,38 @@ // Copyright © 2016 Nicholas Hurden. All rights reserved. // -import Foundation +import AppKit extension NSEvent { var isKeyEvent: Bool { - return subtype.rawValue == 8 + subtype.rawValue == 8 } var keycode: Keycode { - return Keycode((data1 & 0xffff0000) >> 16) + Keycode((data1 & 0xFFFF_0000) >> 16) } var keyEvent: KeyEvent { - let keyFlags = KeyFlags(data1 & 0x0000ffff) - let keyPressed = ((keyFlags & 0xff00) >> 8) == 0xa + let keyFlags = KeyFlags(data1 & 0x0000_FFFF) + let keyPressed = ((keyFlags & 0xFF00) >> 8) == 0xA let keyRepeat = (keyFlags & 0x1) == 0x1 return KeyEvent(keycode: keycode, keyFlags: keyFlags, keyPressed: keyPressed, keyRepeat: keyRepeat) } var isMediaKeyEvent: Bool { - let mediaKeys = [NX_KEYTYPE_PLAY, NX_KEYTYPE_PREVIOUS, NX_KEYTYPE_NEXT, NX_KEYTYPE_FAST, NX_KEYTYPE_REWIND] + let mediaKeys = [ + NX_KEYTYPE_PLAY, + NX_KEYTYPE_PREVIOUS, + NX_KEYTYPE_NEXT, + NX_KEYTYPE_FAST, + NX_KEYTYPE_REWIND, + NX_KEYTYPE_BRIGHTNESS_UP, + NX_KEYTYPE_BRIGHTNESS_DOWN, + NX_KEYTYPE_SOUND_UP, + NX_KEYTYPE_SOUND_DOWN, + NX_KEYTYPE_MUTE, + ] return isKeyEvent && mediaKeys.contains(keycode) } } diff --git a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/project.pbxproj b/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/project.pbxproj index 4c7ada4..ea17f6a 100644 --- a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/project.pbxproj +++ b/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/project.pbxproj @@ -56,11 +56,11 @@ isa = PBXGroup; children = ( E18A87F21C7A237B00825AE3 /* AppDelegate.swift */, - E18A87F41C7A237B00825AE3 /* ViewController.swift */, E18A87F61C7A237B00825AE3 /* Assets.xcassets */, - E18A87F81C7A237B00825AE3 /* Main.storyboard */, E18A87FB1C7A237B00825AE3 /* Info.plist */, + E18A87F81C7A237B00825AE3 /* Main.storyboard */, E18A88071C7A23DD00825AE3 /* MediaKeyTap.framework */, + E18A87F41C7A237B00825AE3 /* ViewController.swift */, ); path = MediaKeyTapExample; sourceTree = ""; @@ -92,18 +92,18 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0910; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Nicholas Hurden"; TargetAttributes = { E18A87EE1C7A237B00825AE3 = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 0910; + LastSwiftMigration = 1020; }; }; }; buildConfigurationList = E18A87EA1C7A237B00825AE3 /* Build configuration list for PBXProject "MediaKeyTapExample" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -136,8 +136,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E18A87F51C7A237B00825AE3 /* ViewController.swift in Sources */, E18A87F31C7A237B00825AE3 /* AppDelegate.swift in Sources */, + E18A87F51C7A237B00825AE3 /* ViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -159,6 +159,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -167,12 +168,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -211,6 +214,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -219,12 +223,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -261,8 +267,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nhurden.MediaKeyTapExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -275,8 +280,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nhurden.MediaKeyTapExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/MediaKeyTapExample.xcscheme b/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/MediaKeyTapExample.xcscheme deleted file mode 100644 index bc98ac4..0000000 --- a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/MediaKeyTapExample.xcscheme +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/xcschememanagement.plist b/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 3e1f950..0000000 --- a/MediaKeyTapExample/MediaKeyTapExample.xcodeproj/xcuserdata/Nicholas.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - MediaKeyTapExample.xcscheme - - orderHint - 1 - - - SuppressBuildableAutocreation - - E18A87EE1C7A237B00825AE3 - - primary - - - - - diff --git a/MediaKeyTapExample/MediaKeyTapExample/AppDelegate.swift b/MediaKeyTapExample/MediaKeyTapExample/AppDelegate.swift index 27e3385..a21a44a 100644 --- a/MediaKeyTapExample/MediaKeyTapExample/AppDelegate.swift +++ b/MediaKeyTapExample/MediaKeyTapExample/AppDelegate.swift @@ -10,6 +10,5 @@ import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - func applicationDidFinishLaunching(_ aNotification: Notification) { - } + func applicationDidFinishLaunching(_: Notification) {} } diff --git a/MediaKeyTapExample/MediaKeyTapExample/ViewController.swift b/MediaKeyTapExample/MediaKeyTapExample/ViewController.swift index 898729f..1800af1 100644 --- a/MediaKeyTapExample/MediaKeyTapExample/ViewController.swift +++ b/MediaKeyTapExample/MediaKeyTapExample/ViewController.swift @@ -10,44 +10,57 @@ import Cocoa import MediaKeyTap class ViewController: NSViewController { - @IBOutlet weak var playPauseLabel: NSTextField! - @IBOutlet weak var previousLabel: NSTextField! - @IBOutlet weak var rewindLabel: NSTextField! - @IBOutlet weak var nextLabel: NSTextField! - @IBOutlet weak var fastForwardLabel: NSTextField! + @IBOutlet var playPauseLabel: NSTextField! + @IBOutlet var previousLabel: NSTextField! + @IBOutlet var rewindLabel: NSTextField! + @IBOutlet var nextLabel: NSTextField! + @IBOutlet var fastForwardLabel: NSTextField! - var mediaKeyTap: MediaKeyTap? + var mediaKeyTap: MediaKeyTap? - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - mediaKeyTap = MediaKeyTap(delegate: self, on: .keyDownAndUp) - mediaKeyTap?.start() - } + self.mediaKeyTap = MediaKeyTap(delegate: self, on: .keyDownAndUp) + self.mediaKeyTap?.start() + } - func toggleLabel(_ label: NSTextField, enabled: Bool) { - label.textColor = enabled ? NSColor.green : NSColor.textColor - } + func toggleLabel(_ label: NSTextField, enabled: Bool) { + label.textColor = enabled ? NSColor.green : NSColor.textColor + } } extension ViewController: MediaKeyTapDelegate { - func handle(mediaKey: MediaKey, event: KeyEvent) { - switch mediaKey { - case .playPause: - print("Play/pause pressed") - toggleLabel(playPauseLabel, enabled: event.keyPressed) - case .previous: - print("Previous pressed") - toggleLabel(previousLabel, enabled: event.keyPressed) - case .rewind: - print("Rewind pressed") - toggleLabel(rewindLabel, enabled: event.keyPressed) - case .next: - print("Next pressed") - toggleLabel(nextLabel, enabled: event.keyPressed) - case .fastForward: - print("Fast Forward pressed") - toggleLabel(fastForwardLabel, enabled: event.keyPressed) - } + func handle(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) { + if modifiers?.isSuperset(of: NSEvent.ModifierFlags([.shift, .option])) ?? false { + print("Shift + Option pressed") + } + switch mediaKey { + case .playPause: + print("Play/pause pressed") + self.toggleLabel(self.playPauseLabel, enabled: event?.keyPressed ?? false) + case .previous: + print("Previous pressed") + self.toggleLabel(self.previousLabel, enabled: event?.keyPressed ?? false) + case .rewind: + print("Rewind pressed") + self.toggleLabel(self.rewindLabel, enabled: event?.keyPressed ?? false) + case .next: + print("Next pressed") + self.toggleLabel(self.nextLabel, enabled: event?.keyPressed ?? false) + case .fastForward: + print("Fast Forward pressed") + self.toggleLabel(self.fastForwardLabel, enabled: event?.keyPressed ?? false) + case .brightnessUp: + print("Brightness up pressed") + case .brightnessDown: + print("Brightness down pressed") + case .volumeUp: + print("Volume up pressed") + case .volumeDown: + print("Volume down pressed") + case .mute: + print("Mute pressed") } + } } diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..f28943e --- /dev/null +++ b/Package.swift @@ -0,0 +1,13 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "MediaKeyTap", + products: [ + .library(name: "MediaKeyTap", targets: ["MediaKeyTap"]), + ], + targets: [ + .target(name: "MediaKeyTap", path: "MediaKeyTap"), + ] +) diff --git a/README.md b/README.md index 5b75fe5..52dd66f 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,11 @@ The MediaKeyTap initialiser allows the keypress behaviour to be specified: ## Requirements -* In order to capture key events globally, your application cannot be sandboxed or you will not receive any events. +In order to capture key events globally, you need to ensure: + +* Your application is not sandboxed +* Your info.plist provides `NSAppleEventsUsageDescription` +* Your application has accessibility privileges (will be asked automatically) ## Installation