Add `.setShortcut()`, `.getShortcut()`, and `.reset()` methods (#24)

This commit is contained in:
Sindre Sorhus 2020-08-28 20:19:32 +02:00 committed by GitHub
parent 729002c51a
commit 43d7fe58b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 524 additions and 152 deletions

View File

@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
E3500C3624DEDCEF00F4B055 /* KeyboardShortcutsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3500C3524DEDCEF00F4B055 /* KeyboardShortcutsTests.swift */; };
E3500C3824DEDCEF00F4B055 /* KeyboardShortcuts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */; };
E3500C3F24DEDDEC00F4B055 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3500C3E24DEDDEC00F4B055 /* Utilities.swift */; };
E38103FE246449180023E9A8 /* Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38103FD246449180023E9A8 /* Name.swift */; };
E3AD497024705C7600F51C0D /* NSMenuItem++.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3AD496F24705C7600F51C0D /* NSMenuItem++.swift */; };
E3BF5627245C23840024D9BF /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF5626245C23840024D9BF /* Recorder.swift */; };
@ -26,6 +29,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
E3500C3924DEDCEF00F4B055 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = "KeyboardShortcuts::KeyboardShortcuts";
remoteInfo = KeyboardShortcuts;
};
E3BF5648245C2D550024D9BF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = OBJ_1 /* Project object */;
@ -50,6 +60,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
E3500C3324DEDCEF00F4B055 /* KeyboardShortcutsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KeyboardShortcutsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
E3500C3524DEDCEF00F4B055 /* KeyboardShortcutsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyboardShortcutsTests.swift; sourceTree = "<group>"; usesTabs = 1; };
E3500C3724DEDCEF00F4B055 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E3500C3E24DEDDEC00F4B055 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Utilities.swift; sourceTree = "<group>"; usesTabs = 1; };
E38103FD246449180023E9A8 /* Name.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Name.swift; sourceTree = "<group>"; usesTabs = 1; };
E3AD496F24705C7600F51C0D /* NSMenuItem++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "NSMenuItem++.swift"; sourceTree = "<group>"; usesTabs = 1; };
E3BF5626245C23840024D9BF /* Recorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Recorder.swift; sourceTree = "<group>"; usesTabs = 1; };
@ -72,6 +86,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
E3500C3024DEDCEF00F4B055 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E3500C3824DEDCEF00F4B055 /* KeyboardShortcuts.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E3BF562F245C2BB30024D9BF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -90,6 +112,16 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E3500C3424DEDCEF00F4B055 /* KeyboardShortcutsTests */ = {
isa = PBXGroup;
children = (
E3500C3524DEDCEF00F4B055 /* KeyboardShortcutsTests.swift */,
E3500C3E24DEDDEC00F4B055 /* Utilities.swift */,
E3500C3724DEDCEF00F4B055 /* Info.plist */,
);
path = KeyboardShortcutsTests;
sourceTree = "<group>";
};
E3BF5633245C2BB30024D9BF /* KeyboardShortcutsExample */ = {
isa = PBXGroup;
children = (
@ -110,18 +142,12 @@
name = Frameworks;
sourceTree = "<group>";
};
OBJ_11 /* Tests */ = {
isa = PBXGroup;
children = (
);
name = Tests;
sourceTree = SOURCE_ROOT;
};
OBJ_12 /* Products */ = {
isa = PBXGroup;
children = (
"KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */,
E3BF5632245C2BB30024D9BF /* KeyboardShortcutsExample.app */,
E3500C3324DEDCEF00F4B055 /* KeyboardShortcutsTests.xctest */,
);
name = Products;
sourceTree = BUILT_PRODUCTS_DIR;
@ -132,8 +158,8 @@
OBJ_16 /* readme.md */,
OBJ_6 /* Package.swift */,
OBJ_7 /* Sources */,
OBJ_11 /* Tests */,
E3BF5633245C2BB30024D9BF /* KeyboardShortcutsExample */,
E3500C3424DEDCEF00F4B055 /* KeyboardShortcutsTests */,
OBJ_12 /* Products */,
E3BF5645245C2D550024D9BF /* Frameworks */,
);
@ -167,6 +193,24 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
E3500C3224DEDCEF00F4B055 /* KeyboardShortcutsTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = E3500C3D24DEDCEF00F4B055 /* Build configuration list for PBXNativeTarget "KeyboardShortcutsTests" */;
buildPhases = (
E3500C2F24DEDCEF00F4B055 /* Sources */,
E3500C3024DEDCEF00F4B055 /* Frameworks */,
E3500C3124DEDCEF00F4B055 /* Resources */,
);
buildRules = (
);
dependencies = (
E3500C3A24DEDCEF00F4B055 /* PBXTargetDependency */,
);
name = KeyboardShortcutsTests;
productName = KeyboardShortcutsTests;
productReference = E3500C3324DEDCEF00F4B055 /* KeyboardShortcutsTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
E3BF5631245C2BB30024D9BF /* KeyboardShortcutsExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = E3BF5642245C2BB50024D9BF /* Build configuration list for PBXNativeTarget "KeyboardShortcutsExample" */;
@ -225,9 +269,12 @@
isa = PBXProject;
attributes = {
LastSwiftMigration = 9999;
LastSwiftUpdateCheck = 1140;
LastSwiftUpdateCheck = 1160;
LastUpgradeCheck = 1150;
TargetAttributes = {
E3500C3224DEDCEF00F4B055 = {
CreatedOnToolsVersion = 11.6;
};
E3BF5631245C2BB30024D9BF = {
CreatedOnToolsVersion = 11.4.1;
};
@ -249,11 +296,19 @@
"KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */,
"KeyboardShortcuts::SwiftPMPackageDescription" /* KeyboardShortcutsPackageDescription */,
E3BF5631245C2BB30024D9BF /* KeyboardShortcutsExample */,
E3500C3224DEDCEF00F4B055 /* KeyboardShortcutsTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E3500C3124DEDCEF00F4B055 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
E3BF5630245C2BB30024D9BF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -305,6 +360,15 @@
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
E3500C2F24DEDCEF00F4B055 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E3500C3F24DEDDEC00F4B055 /* Utilities.swift in Sources */,
E3500C3624DEDCEF00F4B055 /* KeyboardShortcutsTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
E3BF562E245C2BB30024D9BF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -341,6 +405,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
E3500C3A24DEDCEF00F4B055 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = "KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */;
targetProxy = E3500C3924DEDCEF00F4B055 /* PBXContainerItemProxy */;
};
E3BF5649245C2D550024D9BF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = "KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */;
@ -360,6 +429,84 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
E3500C3B24DEDCEF00F4B055 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = YG56YK5RN5;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeyboardShortcutsTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
E3500C3C24DEDCEF00F4B055 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = YG56YK5RN5;
ENABLE_NS_ASSERTIONS = NO;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeyboardShortcutsTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
E3BF5643245C2BB50024D9BF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -703,6 +850,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
E3500C3D24DEDCEF00F4B055 /* Build configuration list for PBXNativeTarget "KeyboardShortcutsTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E3500C3B24DEDCEF00F4B055 /* Debug */,
E3500C3C24DEDCEF00F4B055 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E3BF5642245C2BB50024D9BF /* Build configuration list for PBXNativeTarget "KeyboardShortcutsExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1160"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E3500C3224DEDCEF00F4B055"
BuildableName = "KeyboardShortcutsTests.xctest"
BlueprintName = "KeyboardShortcutsTests"
ReferencedContainer = "container:KeyboardShortcuts.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -24,6 +24,11 @@ struct ContentView: View {
Text("Pressed? \(isPressed2 ? "👍" : "👎")")
.frame(width: 100, alignment: .leading)
}
Spacer()
Divider()
Button("Reset All") {
KeyboardShortcuts.reset(.testShortcut1, .testShortcut2)
}
}
.frame(maxWidth: 300)
.padding(60)

View File

@ -0,0 +1,22 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</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>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,30 @@
import XCTest
import KeyboardShortcuts
final class KeyboardShortcutsTests: XCTestCase {
// TODO: Add more tests.
override func setUpWithError() throws {
UserDefaults.standard.removeAll()
}
func testSetShortcutAndReset() throws {
let defaultShortcut = KeyboardShortcuts.Shortcut(.c)
let shortcut1 = KeyboardShortcuts.Shortcut(.a)
let shortcut2 = KeyboardShortcuts.Shortcut(.b)
let shortcutName1 = KeyboardShortcuts.Name("testSetShortcutAndReset1")
let shortcutName2 = KeyboardShortcuts.Name("testSetShortcutAndReset2", default: defaultShortcut)
KeyboardShortcuts.setShortcut(shortcut1, for: shortcutName1)
KeyboardShortcuts.setShortcut(shortcut2, for: shortcutName2)
XCTAssertEqual(KeyboardShortcuts.getShortcut(for: shortcutName1), shortcut1)
XCTAssertEqual(KeyboardShortcuts.getShortcut(for: shortcutName2), shortcut2)
KeyboardShortcuts.reset(shortcutName1, shortcutName2)
XCTAssertNil(KeyboardShortcuts.getShortcut(for: shortcutName1))
XCTAssertEqual(KeyboardShortcuts.getShortcut(for: shortcutName2), defaultShortcut)
}
}

View File

@ -0,0 +1,14 @@
import Foundation
extension UserDefaults {
/**
Remove all entries.
- Note: This only removes user-defined entries. System-defined entries will remain.
*/
public func removeAll() {
for key in dictionaryRepresentation().keys {
removeObject(forKey: key)
}
}
}

View File

@ -5,135 +5,135 @@ extension KeyboardShortcuts {
// swiftlint:disable identifier_name
/// Represents a key on the keyboard.
public struct Key: Hashable, RawRepresentable {
static let a = Self(kVK_ANSI_A)
static let b = Self(kVK_ANSI_B)
static let c = Self(kVK_ANSI_C)
static let d = Self(kVK_ANSI_D)
static let e = Self(kVK_ANSI_E)
static let f = Self(kVK_ANSI_F)
static let g = Self(kVK_ANSI_G)
static let h = Self(kVK_ANSI_H)
static let i = Self(kVK_ANSI_I)
static let j = Self(kVK_ANSI_J)
static let k = Self(kVK_ANSI_K)
static let l = Self(kVK_ANSI_L)
static let m = Self(kVK_ANSI_M)
static let n = Self(kVK_ANSI_N)
static let o = Self(kVK_ANSI_O)
static let p = Self(kVK_ANSI_P)
static let q = Self(kVK_ANSI_Q)
static let r = Self(kVK_ANSI_R)
static let s = Self(kVK_ANSI_S)
static let t = Self(kVK_ANSI_T)
static let u = Self(kVK_ANSI_U)
static let v = Self(kVK_ANSI_V)
static let w = Self(kVK_ANSI_W)
static let x = Self(kVK_ANSI_X)
static let y = Self(kVK_ANSI_Y)
static let z = Self(kVK_ANSI_Z)
public static let a = Self(kVK_ANSI_A)
public static let b = Self(kVK_ANSI_B)
public static let c = Self(kVK_ANSI_C)
public static let d = Self(kVK_ANSI_D)
public static let e = Self(kVK_ANSI_E)
public static let f = Self(kVK_ANSI_F)
public static let g = Self(kVK_ANSI_G)
public static let h = Self(kVK_ANSI_H)
public static let i = Self(kVK_ANSI_I)
public static let j = Self(kVK_ANSI_J)
public static let k = Self(kVK_ANSI_K)
public static let l = Self(kVK_ANSI_L)
public static let m = Self(kVK_ANSI_M)
public static let n = Self(kVK_ANSI_N)
public static let o = Self(kVK_ANSI_O)
public static let p = Self(kVK_ANSI_P)
public static let q = Self(kVK_ANSI_Q)
public static let r = Self(kVK_ANSI_R)
public static let s = Self(kVK_ANSI_S)
public static let t = Self(kVK_ANSI_T)
public static let u = Self(kVK_ANSI_U)
public static let v = Self(kVK_ANSI_V)
public static let w = Self(kVK_ANSI_W)
public static let x = Self(kVK_ANSI_X)
public static let y = Self(kVK_ANSI_Y)
public static let z = Self(kVK_ANSI_Z)
// swiftlint:enable identifier_name
// MARK: Numbers
static let zero = Self(kVK_ANSI_0)
static let one = Self(kVK_ANSI_1)
static let two = Self(kVK_ANSI_2)
static let three = Self(kVK_ANSI_3)
static let four = Self(kVK_ANSI_4)
static let five = Self(kVK_ANSI_5)
static let six = Self(kVK_ANSI_6)
static let seven = Self(kVK_ANSI_7)
static let eight = Self(kVK_ANSI_8)
static let nine = Self(kVK_ANSI_9)
public static let zero = Self(kVK_ANSI_0)
public static let one = Self(kVK_ANSI_1)
public static let two = Self(kVK_ANSI_2)
public static let three = Self(kVK_ANSI_3)
public static let four = Self(kVK_ANSI_4)
public static let five = Self(kVK_ANSI_5)
public static let six = Self(kVK_ANSI_6)
public static let seven = Self(kVK_ANSI_7)
public static let eight = Self(kVK_ANSI_8)
public static let nine = Self(kVK_ANSI_9)
// MARK: Modifiers
static let capsLock = Self(kVK_CapsLock)
static let shift = Self(kVK_Shift)
static let function = Self(kVK_Function)
static let control = Self(kVK_Control)
static let option = Self(kVK_Option)
static let command = Self(kVK_Command)
static let rightCommand = Self(kVK_RightCommand)
static let rightOption = Self(kVK_RightOption)
static let rightControl = Self(kVK_RightControl)
static let rightShift = Self(kVK_RightShift)
public static let capsLock = Self(kVK_CapsLock)
public static let shift = Self(kVK_Shift)
public static let function = Self(kVK_Function)
public static let control = Self(kVK_Control)
public static let option = Self(kVK_Option)
public static let command = Self(kVK_Command)
public static let rightCommand = Self(kVK_RightCommand)
public static let rightOption = Self(kVK_RightOption)
public static let rightControl = Self(kVK_RightControl)
public static let rightShift = Self(kVK_RightShift)
// MARK: Miscellaneous
static let `return` = Self(kVK_Return)
static let backslash = Self(kVK_ANSI_Backslash)
static let backtick = Self(kVK_ANSI_Grave)
static let comma = Self(kVK_ANSI_Comma)
static let equal = Self(kVK_ANSI_Equal)
static let minus = Self(kVK_ANSI_Minus)
static let period = Self(kVK_ANSI_Period)
static let quote = Self(kVK_ANSI_Quote)
static let semicolon = Self(kVK_ANSI_Semicolon)
static let slash = Self(kVK_ANSI_Slash)
static let space = Self(kVK_Space)
static let tab = Self(kVK_Tab)
static let leftBracket = Self(kVK_ANSI_LeftBracket)
static let rightBracket = Self(kVK_ANSI_RightBracket)
static let pageUp = Self(kVK_PageUp)
static let pageDown = Self(kVK_PageDown)
static let home = Self(kVK_Home)
static let end = Self(kVK_End)
static let upArrow = Self(kVK_UpArrow)
static let rightArrow = Self(kVK_RightArrow)
static let downArrow = Self(kVK_DownArrow)
static let leftArrow = Self(kVK_LeftArrow)
static let escape = Self(kVK_Escape)
static let delete = Self(kVK_Delete)
static let deleteForward = Self(kVK_ForwardDelete)
static let help = Self(kVK_Help)
static let mute = Self(kVK_Mute)
static let volumeUp = Self(kVK_VolumeUp)
static let volumeDown = Self(kVK_VolumeDown)
public static let `return` = Self(kVK_Return)
public static let backslash = Self(kVK_ANSI_Backslash)
public static let backtick = Self(kVK_ANSI_Grave)
public static let comma = Self(kVK_ANSI_Comma)
public static let equal = Self(kVK_ANSI_Equal)
public static let minus = Self(kVK_ANSI_Minus)
public static let period = Self(kVK_ANSI_Period)
public static let quote = Self(kVK_ANSI_Quote)
public static let semicolon = Self(kVK_ANSI_Semicolon)
public static let slash = Self(kVK_ANSI_Slash)
public static let space = Self(kVK_Space)
public static let tab = Self(kVK_Tab)
public static let leftBracket = Self(kVK_ANSI_LeftBracket)
public static let rightBracket = Self(kVK_ANSI_RightBracket)
public static let pageUp = Self(kVK_PageUp)
public static let pageDown = Self(kVK_PageDown)
public static let home = Self(kVK_Home)
public static let end = Self(kVK_End)
public static let upArrow = Self(kVK_UpArrow)
public static let rightArrow = Self(kVK_RightArrow)
public static let downArrow = Self(kVK_DownArrow)
public static let leftArrow = Self(kVK_LeftArrow)
public static let escape = Self(kVK_Escape)
public static let delete = Self(kVK_Delete)
public static let deleteForward = Self(kVK_ForwardDelete)
public static let help = Self(kVK_Help)
public static let mute = Self(kVK_Mute)
public static let volumeUp = Self(kVK_VolumeUp)
public static let volumeDown = Self(kVK_VolumeDown)
// MARK: Function
static let f1 = Self(kVK_F1)
static let f2 = Self(kVK_F2)
static let f3 = Self(kVK_F3)
static let f4 = Self(kVK_F4)
static let f5 = Self(kVK_F5)
static let f6 = Self(kVK_F6)
static let f7 = Self(kVK_F7)
static let f8 = Self(kVK_F8)
static let f9 = Self(kVK_F9)
static let f10 = Self(kVK_F10)
static let f11 = Self(kVK_F11)
static let f12 = Self(kVK_F12)
static let f13 = Self(kVK_F13)
static let f14 = Self(kVK_F14)
static let f15 = Self(kVK_F15)
static let f16 = Self(kVK_F16)
static let f17 = Self(kVK_F17)
static let f18 = Self(kVK_F18)
static let f19 = Self(kVK_F19)
static let f20 = Self(kVK_F20)
public static let f1 = Self(kVK_F1)
public static let f2 = Self(kVK_F2)
public static let f3 = Self(kVK_F3)
public static let f4 = Self(kVK_F4)
public static let f5 = Self(kVK_F5)
public static let f6 = Self(kVK_F6)
public static let f7 = Self(kVK_F7)
public static let f8 = Self(kVK_F8)
public static let f9 = Self(kVK_F9)
public static let f10 = Self(kVK_F10)
public static let f11 = Self(kVK_F11)
public static let f12 = Self(kVK_F12)
public static let f13 = Self(kVK_F13)
public static let f14 = Self(kVK_F14)
public static let f15 = Self(kVK_F15)
public static let f16 = Self(kVK_F16)
public static let f17 = Self(kVK_F17)
public static let f18 = Self(kVK_F18)
public static let f19 = Self(kVK_F19)
public static let f20 = Self(kVK_F20)
// MARK: Keypad
static let keypad0 = Self(kVK_ANSI_Keypad0)
static let keypad1 = Self(kVK_ANSI_Keypad1)
static let keypad2 = Self(kVK_ANSI_Keypad2)
static let keypad3 = Self(kVK_ANSI_Keypad3)
static let keypad4 = Self(kVK_ANSI_Keypad4)
static let keypad5 = Self(kVK_ANSI_Keypad5)
static let keypad6 = Self(kVK_ANSI_Keypad6)
static let keypad7 = Self(kVK_ANSI_Keypad7)
static let keypad8 = Self(kVK_ANSI_Keypad8)
static let keypad9 = Self(kVK_ANSI_Keypad9)
static let keypadClear = Self(kVK_ANSI_KeypadClear)
static let keypadDecimal = Self(kVK_ANSI_KeypadDecimal)
static let keypadDivide = Self(kVK_ANSI_KeypadDivide)
static let keypadEnter = Self(kVK_ANSI_KeypadEnter)
static let keypadEquals = Self(kVK_ANSI_KeypadEquals)
static let keypadMinus = Self(kVK_ANSI_KeypadMinus)
static let keypadMultiply = Self(kVK_ANSI_KeypadMultiply)
static let keypadPlus = Self(kVK_ANSI_KeypadPlus)
public static let keypad0 = Self(kVK_ANSI_Keypad0)
public static let keypad1 = Self(kVK_ANSI_Keypad1)
public static let keypad2 = Self(kVK_ANSI_Keypad2)
public static let keypad3 = Self(kVK_ANSI_Keypad3)
public static let keypad4 = Self(kVK_ANSI_Keypad4)
public static let keypad5 = Self(kVK_ANSI_Keypad5)
public static let keypad6 = Self(kVK_ANSI_Keypad6)
public static let keypad7 = Self(kVK_ANSI_Keypad7)
public static let keypad8 = Self(kVK_ANSI_Keypad8)
public static let keypad9 = Self(kVK_ANSI_Keypad9)
public static let keypadClear = Self(kVK_ANSI_KeypadClear)
public static let keypadDecimal = Self(kVK_ANSI_KeypadDecimal)
public static let keypadDivide = Self(kVK_ANSI_KeypadDivide)
public static let keypadEnter = Self(kVK_ANSI_KeypadEnter)
public static let keypadEquals = Self(kVK_ANSI_KeypadEquals)
public static let keypadMinus = Self(kVK_ANSI_KeypadMinus)
public static let keypadMultiply = Self(kVK_ANSI_KeypadMultiply)
public static let keypadPlus = Self(kVK_ANSI_KeypadPlus)
// MARK: Properties

View File

@ -51,7 +51,7 @@ public enum KeyboardShortcuts {
Disable a keyboard shortcut.
*/
public static func disable(_ name: Name) {
guard let shortcut = userDefaultsGet(name: name) else {
guard let shortcut = getShortcut(for: name) else {
return
}
@ -62,13 +62,103 @@ public enum KeyboardShortcuts {
Enable a disabled keyboard shortcut.
*/
public static func enable(_ name: Name) {
guard let shortcut = userDefaultsGet(name: name) else {
guard let shortcut = getShortcut(for: name) else {
return
}
register(shortcut)
}
/**
Reset the keyboard shortcut for one or more names.
If the `Name` has a default shortcut, it will reset to that.
```
import SwiftUI
import KeyboardShortcuts
struct PreferencesView: View {
var body: some View {
VStack {
//
Button("Reset All") {
KeyboardShortcuts.reset(
.toggleUnicornMode,
.showRainbow
)
}
}
}
}
```
*/
public static func reset(_ names: Name...) {
reset(names)
}
/**
Reset the keyboard shortcut for one or more names.
If the `Name` has a default shortcut, it will reset to that.
- Note: This overload exists as Swift doesn't support splatting.
```
import SwiftUI
import KeyboardShortcuts
struct PreferencesView: View {
var body: some View {
VStack {
//
Button("Reset All") {
KeyboardShortcuts.reset(
.toggleUnicornMode,
.showRainbow
)
}
}
}
}
```
*/
public static func reset(_ names: [Name]) {
for name in names {
setShortcut(name.defaultShortcut, for: name)
}
}
/**
Set the keyboard shortcut for a name.
Setting it to `nil` removes the shortcut, even if the `Name` has a default shortcut defined. Use `.reset()` if you want it to respect the default shortcut.
You would usually not need this as the user would be the one setting the shortcut in a preferences user-interface, but it can be useful when, for example, migrating from a different keyboard shortcuts package.
*/
public static func setShortcut(_ shortcut: Shortcut?, for name: Name) {
guard let shortcut = shortcut else {
userDefaultsRemove(name: name)
return
}
userDefaultsSet(name: name, shortcut: shortcut)
}
/**
Get the keyboard shortcut for a name.
*/
public static func getShortcut(for name: Name) -> Shortcut? {
guard
let data = UserDefaults.standard.string(forKey: userDefaultsKey(for: name))?.data(using: .utf8),
let decoded = try? JSONDecoder().decode(Shortcut.self, from: data)
else {
return nil
}
return decoded
}
private static func handleOnKeyDown(_ shortcut: Shortcut) {
guard !isPaused else {
return
@ -81,7 +171,7 @@ public enum KeyboardShortcuts {
}
for (name, handlers) in userDefaultsKeyDownHandlers {
guard userDefaultsGet(name: name) == shortcut else {
guard getShortcut(for: name) == shortcut else {
continue
}
@ -103,7 +193,7 @@ public enum KeyboardShortcuts {
}
for (name, handlers) in userDefaultsKeyUpHandlers {
guard userDefaultsGet(name: name) == shortcut else {
guard getShortcut(for: name) == shortcut else {
continue
}
@ -142,7 +232,7 @@ public enum KeyboardShortcuts {
userDefaultsKeyDownHandlers[name]?.append(action)
// If the keyboard shortcut already exist, we register it.
if let shortcut = userDefaultsGet(name: name) {
if let shortcut = getShortcut(for: name) {
register(shortcut)
}
}
@ -176,7 +266,7 @@ public enum KeyboardShortcuts {
userDefaultsKeyUpHandlers[name]?.append(action)
// If the keyboard shortcut already exist, we register it.
if let shortcut = userDefaultsGet(name: name) {
if let shortcut = getShortcut(for: name) {
register(shortcut)
}
}
@ -191,24 +281,12 @@ public enum KeyboardShortcuts {
NotificationCenter.default.post(name: .shortcutByNameDidChange, object: nil, userInfo: ["name": name])
}
// TODO: Should these be on `Shortcut` instead?
static func userDefaultsGet(name: Name) -> Shortcut? {
guard
let data = UserDefaults.standard.string(forKey: userDefaultsKey(for: name))?.data(using: .utf8),
let decoded = try? JSONDecoder().decode(Shortcut.self, from: data)
else {
return nil
}
return decoded
}
static func userDefaultsSet(name: Name, shortcut: Shortcut) {
guard let encoded = try? JSONEncoder().encode(shortcut).string else {
return
}
if let oldShortcut = userDefaultsGet(name: name) {
if let oldShortcut = getShortcut(for: name) {
unregister(oldShortcut)
}
@ -218,7 +296,7 @@ public enum KeyboardShortcuts {
}
static func userDefaultsRemove(name: Name) {
guard let shortcut = userDefaultsGet(name: name) else {
guard let shortcut = getShortcut(for: name) else {
return
}

View File

@ -18,19 +18,21 @@ extension KeyboardShortcuts {
public typealias Shortcut = KeyboardShortcuts.Shortcut
public let rawValue: String
public let defaultShortcut: Shortcut?
/**
- Parameter name: Name of the shortcut.
- Parameter default: Optional default key combination for the shortcut. Do not set this unless it's essential. Users find it annoying when random apps steal their existing keyboard shortcuts. It's generally better to show a welcome screen on the first app launch that lets the user set the shortcut.
- Parameter default: Optional default key combination. Do not set this unless it's essential. Users find it annoying when random apps steal their existing keyboard shortcuts. It's generally better to show a welcome screen on the first app launch that lets the user set the shortcut.
*/
public init(_ name: String, default defaultShortcut: Shortcut? = nil) {
self.rawValue = name
self.defaultShortcut = defaultShortcut
if
let defaultShortcut = defaultShortcut,
!userDefaultsContains(name: self)
{
userDefaultsSet(name: self, shortcut: defaultShortcut)
setShortcut(defaultShortcut, for: self)
}
}
}

View File

@ -30,6 +30,7 @@ extension KeyboardShortcuts {
private var eventMonitor: LocalEventMonitor?
private let shortcutName: Name
private let onChange: ((_ shortcut: Shortcut?) -> Void)?
private var observer: NSObjectProtocol?
/// :nodoc:
override public var canBecomeKeyView: Bool { false }
@ -68,7 +69,7 @@ extension KeyboardShortcuts {
self.alignment = .center
(self.cell as? NSSearchFieldCell)?.searchButtonCell = nil
if let shortcut = userDefaultsGet(name: shortcutName) {
if let shortcut = getShortcut(for: shortcutName) {
self.stringValue = "\(shortcut)"
}
@ -81,6 +82,8 @@ extension KeyboardShortcuts {
// Hide the cancel button when not showing the shortcut so the placeholder text is properly centered. Must be last.
self.cancelButton = (self.cell as? NSSearchFieldCell)?.cancelButtonCell
self.showsCancelButton = !stringValue.isEmpty
setUpEvents()
}
@available(*, unavailable)
@ -88,6 +91,21 @@ extension KeyboardShortcuts {
fatalError("init(coder:) has not been implemented")
}
private func setUpEvents() {
observer = NotificationCenter.default.addObserver(forName: .shortcutByNameDidChange, object: nil, queue: nil) { [weak self] notification in
guard
let self = self,
let nameInNotification = notification.userInfo?["name"] as? KeyboardShortcuts.Name,
nameInNotification == self.shortcutName
else {
return
}
self.stringValue = getShortcut(for: nameInNotification).map { "\($0)" } ?? ""
self.showsCancelButton = !self.stringValue.isEmpty
}
}
/// :nodoc:
public func controlTextDidChange(_ object: Notification) {
if stringValue.isEmpty {
@ -222,12 +240,7 @@ extension KeyboardShortcuts {
}
private func saveShortcut(_ shortcut: Shortcut?) {
if let shortcut = shortcut {
userDefaultsSet(name: shortcutName, shortcut: shortcut)
} else {
userDefaultsRemove(name: shortcutName)
}
setShortcut(shortcut, for: shortcutName)
onChange?(shortcut)
}
}

View File

@ -46,7 +46,7 @@ extension KeyboardShortcuts {
/// Initialize from a keyboard shortcut stored by `Recorder` or `RecorderCocoa`.
public init?(name: Name) {
guard let shortcut = userDefaultsGet(name: name) else {
guard let shortcut = getShortcut(for: name) else {
return nil
}