Init
This commit is contained in:
commit
affae1cd71
|
@ -0,0 +1,12 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1 @@
|
|||
* text=auto eol=lf
|
|
@ -0,0 +1,3 @@
|
|||
github: sindresorhus
|
||||
open_collective: sindresorhus
|
||||
custom: https://sindresorhus.com/donate
|
|
@ -0,0 +1,14 @@
|
|||
name: Public docs
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish docs
|
||||
uses: steven0351/publish-jazzy-docs@v1
|
||||
with:
|
||||
personal_access_token: ${{ secrets.ACCESS_TOKEN }}
|
||||
config: .jazzy.yml
|
|
@ -0,0 +1,14 @@
|
|||
name: SwiftLint
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/swiftlint.yml'
|
||||
- '.swiftlint.yml'
|
||||
- '**/*.swift'
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3
|
|
@ -0,0 +1,4 @@
|
|||
/.build
|
||||
/Packages
|
||||
xcuserdata
|
||||
project.xcworkspace
|
|
@ -0,0 +1,12 @@
|
|||
module: KeyboardShortcuts
|
||||
github_url: https://github.com/sindresorhus/KeyboardShortcuts
|
||||
root_url: https://sindresorhus.com/KeyboardShortcuts
|
||||
copyright: MIT License © [Sindre Sorhus](https://sindresorhus.com)
|
||||
swift_build_tool: spm
|
||||
build_tool_arguments:
|
||||
- '-Xswiftc'
|
||||
- '-swift-version'
|
||||
- '-Xswiftc'
|
||||
- '5'
|
||||
theme: fullwidth
|
||||
hide_documentation_coverage: true
|
|
@ -0,0 +1,187 @@
|
|||
whitelist_rules:
|
||||
- anyobject_protocol
|
||||
- array_init
|
||||
- attributes
|
||||
- block_based_kvo
|
||||
- class_delegate_protocol
|
||||
- closing_brace
|
||||
- closure_end_indentation
|
||||
- closure_parameter_position
|
||||
- closure_spacing
|
||||
- collection_alignment
|
||||
- colon
|
||||
- comma
|
||||
- compiler_protocol_init
|
||||
- conditional_returns_on_newline
|
||||
- contains_over_filter_count
|
||||
- contains_over_filter_is_empty
|
||||
- contains_over_first_not_nil
|
||||
- contains_over_range_nil_comparison
|
||||
- control_statement
|
||||
- custom_rules
|
||||
- deployment_target
|
||||
- discarded_notification_center_observer
|
||||
- discouraged_direct_init
|
||||
- discouraged_object_literal
|
||||
- discouraged_optional_boolean
|
||||
- discouraged_optional_collection
|
||||
- duplicate_enum_cases
|
||||
- duplicate_imports
|
||||
- dynamic_inline
|
||||
- empty_collection_literal
|
||||
- empty_count
|
||||
- empty_enum_arguments
|
||||
- empty_parameters
|
||||
- empty_parentheses_with_trailing_closure
|
||||
- empty_string
|
||||
- empty_xctest_method
|
||||
- enum_case_associated_value_count
|
||||
- explicit_init
|
||||
- fallthrough
|
||||
- fatal_error_message
|
||||
- first_where
|
||||
- flatmap_over_map_reduce
|
||||
- for_where
|
||||
- generic_type_name
|
||||
- identical_operands
|
||||
- identifier_name
|
||||
- implicit_getter
|
||||
- implicit_return
|
||||
- inert_defer
|
||||
- is_disjoint
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
- leading_whitespace
|
||||
- legacy_cggeometry_functions
|
||||
- legacy_constant
|
||||
- legacy_constructor
|
||||
- legacy_hashing
|
||||
- legacy_multiple
|
||||
- legacy_nsgeometry_functions
|
||||
- legacy_random
|
||||
- literal_expression_end_indentation
|
||||
- lower_acl_than_parent
|
||||
- mark
|
||||
- modifier_order
|
||||
- multiline_arguments
|
||||
- multiline_function_chains
|
||||
- multiline_literal_brackets
|
||||
- multiline_parameters
|
||||
- multiline_parameters_brackets
|
||||
- nimble_operator
|
||||
- no_extension_access_modifier
|
||||
- no_fallthrough_only
|
||||
- no_space_in_method_call
|
||||
- notification_center_detachment
|
||||
- nsobject_prefer_isequal
|
||||
- number_separator
|
||||
- object_literal
|
||||
- opening_brace
|
||||
- operator_usage_whitespace
|
||||
- operator_whitespace
|
||||
- orphaned_doc_comment
|
||||
- overridden_super_call
|
||||
- pattern_matching_keywords
|
||||
- prefer_self_type_over_type_of_self
|
||||
- private_action
|
||||
- private_outlet
|
||||
- private_unit_test
|
||||
- prohibited_nan_comparison
|
||||
- prohibited_super_call
|
||||
- protocol_property_accessors_order
|
||||
- reduce_boolean
|
||||
- reduce_into
|
||||
- redundant_discardable_let
|
||||
- redundant_nil_coalescing
|
||||
- redundant_objc_attribute
|
||||
- redundant_optional_initialization
|
||||
- redundant_set_access_control
|
||||
- redundant_string_enum_value
|
||||
- redundant_type_annotation
|
||||
- redundant_void_return
|
||||
- required_enum_case
|
||||
- return_value_from_void_function
|
||||
- return_arrow_whitespace
|
||||
- shorthand_operator
|
||||
- sorted_first_last
|
||||
- statement_position
|
||||
- static_operator
|
||||
- strong_iboutlet
|
||||
- superfluous_disable_command
|
||||
- switch_case_alignment
|
||||
- switch_case_on_newline
|
||||
- syntactic_sugar
|
||||
- toggle_bool
|
||||
- trailing_closure
|
||||
- trailing_comma
|
||||
- trailing_newline
|
||||
- trailing_semicolon
|
||||
- trailing_whitespace
|
||||
- tuple_pattern
|
||||
- unavailable_function
|
||||
- unneeded_break_in_switch
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unowned_variable_capture
|
||||
- untyped_error_in_catch
|
||||
- unused_capture_list
|
||||
- unused_closure_parameter
|
||||
- unused_control_flow_label
|
||||
- unused_enumerated
|
||||
- unused_optional_binding
|
||||
- unused_setter_value
|
||||
- valid_ibinspectable
|
||||
- vertical_parameter_alignment
|
||||
- vertical_parameter_alignment_on_call
|
||||
- vertical_whitespace_closing_braces
|
||||
- vertical_whitespace_opening_braces
|
||||
- void_return
|
||||
- weak_delegate
|
||||
- xct_specific_matcher
|
||||
- xctfail_message
|
||||
- yoda_condition
|
||||
analyzer_rules:
|
||||
- unused_declaration
|
||||
- unused_import
|
||||
force_cast: warning
|
||||
force_try: warning
|
||||
force_unwrapping: warning
|
||||
number_separator:
|
||||
minimum_length: 5
|
||||
object_literal:
|
||||
image_literal: false
|
||||
discouraged_object_literal:
|
||||
color_literal: false
|
||||
identifier_name:
|
||||
max_length:
|
||||
warning: 100
|
||||
error: 100
|
||||
min_length:
|
||||
warning: 2
|
||||
error: 2
|
||||
validates_start_with_lowercase: false
|
||||
allowed_symbols:
|
||||
- '_'
|
||||
excluded:
|
||||
- 'x'
|
||||
- 'y'
|
||||
- 'a'
|
||||
- 'b'
|
||||
- 'x1'
|
||||
- 'x2'
|
||||
- 'y1'
|
||||
- 'y2'
|
||||
deployment_target:
|
||||
macOS_deployment_target: '10.11'
|
||||
custom_rules:
|
||||
no_nsrect:
|
||||
regex: '\bNSRect\b'
|
||||
match_kinds: typeidentifier
|
||||
message: 'Use CGRect instead of NSRect'
|
||||
no_nssize:
|
||||
regex: '\bNSSize\b'
|
||||
match_kinds: typeidentifier
|
||||
message: 'Use CGSize instead of NSSize'
|
||||
no_nspoint:
|
||||
regex: '\bNSPoint\b'
|
||||
match_kinds: typeidentifier
|
||||
message: 'Use CGPoint instead of NSPoint'
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,14 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'KeyboardShortcuts'
|
||||
s.version = '0.0.0'
|
||||
s.summary = 'Add user-customizable global keyboard shortcuts to your macOS app in minutes'
|
||||
s.license = 'MIT'
|
||||
s.homepage = 'https://github.com/sindresorhus/KeyboardShortcuts'
|
||||
s.social_media_url = 'https://twitter.com/sindresorhus'
|
||||
s.authors = { 'Sindre Sorhus' => 'sindresorhus@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/sindresorhus/KeyboardShortcuts.git', :tag => "v#{s.version}" }
|
||||
s.source_files = 'Sources/**/*.swift'
|
||||
s.swift_version = '5.2'
|
||||
s.platform = :macos, '10.11'
|
||||
s.weak_framework = 'Combine'
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
<?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>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>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,692 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
E38103FE246449180023E9A8 /* Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = E38103FD246449180023E9A8 /* Name.swift */; };
|
||||
E3BF5627245C23840024D9BF /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF5626245C23840024D9BF /* Recorder.swift */; };
|
||||
E3BF5629245C24450024D9BF /* RecorderCocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF5628245C24450024D9BF /* RecorderCocoa.swift */; };
|
||||
E3BF562B245C28BD0024D9BF /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF562A245C28BD0024D9BF /* Shortcut.swift */; };
|
||||
E3BF562D245C29C30024D9BF /* CarbonKeyboardShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF562C245C29C30024D9BF /* CarbonKeyboardShortcuts.swift */; };
|
||||
E3BF5635245C2BB30024D9BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF5634245C2BB30024D9BF /* AppDelegate.swift */; };
|
||||
E3BF5637245C2BB30024D9BF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF5636245C2BB30024D9BF /* ContentView.swift */; };
|
||||
E3BF5639245C2BB50024D9BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E3BF5638245C2BB50024D9BF /* Assets.xcassets */; };
|
||||
E3BF563F245C2BB50024D9BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E3BF563D245C2BB50024D9BF /* Main.storyboard */; };
|
||||
E3BF5646245C2D550024D9BF /* KeyboardShortcuts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */; };
|
||||
E3BF5647245C2D550024D9BF /* KeyboardShortcuts.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = "KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
E3BF564C245C34B30024D9BF /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3BF564B245C34B30024D9BF /* Key.swift */; };
|
||||
OBJ_23 /* KeyboardShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* KeyboardShortcuts.swift */; };
|
||||
OBJ_24 /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* util.swift */; };
|
||||
OBJ_31 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
E3BF5648245C2D550024D9BF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "KeyboardShortcuts::KeyboardShortcuts";
|
||||
remoteInfo = KeyboardShortcuts;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
E3BF564A245C2D550024D9BF /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
E3BF5647245C2D550024D9BF /* KeyboardShortcuts.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
E38103FD246449180023E9A8 /* Name.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Name.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF5626245C23840024D9BF /* Recorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Recorder.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF5628245C24450024D9BF /* RecorderCocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = RecorderCocoa.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF562A245C28BD0024D9BF /* Shortcut.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Shortcut.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF562C245C29C30024D9BF /* CarbonKeyboardShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CarbonKeyboardShortcuts.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF5632245C2BB30024D9BF /* KeyboardShortcutsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KeyboardShortcutsExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E3BF5634245C2BB30024D9BF /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF5636245C2BB30024D9BF /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ContentView.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
E3BF5638245C2BB50024D9BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
E3BF563E245C2BB50024D9BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
E3BF5640245C2BB50024D9BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E3BF5641245C2BB50024D9BF /* KeyboardShortcutsExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KeyboardShortcutsExample.entitlements; sourceTree = "<group>"; };
|
||||
E3BF564B245C34B30024D9BF /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Key.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
"KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeyboardShortcuts.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
OBJ_10 /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = util.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
OBJ_16 /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = readme.md; sourceTree = "<group>"; usesTabs = 1; };
|
||||
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = Package.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
OBJ_9 /* KeyboardShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = KeyboardShortcuts.swift; sourceTree = "<group>"; usesTabs = 1; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
E3BF562F245C2BB30024D9BF /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E3BF5646245C2D550024D9BF /* KeyboardShortcuts.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_25 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
E3BF5633245C2BB30024D9BF /* KeyboardShortcutsExample */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E3BF5634245C2BB30024D9BF /* AppDelegate.swift */,
|
||||
E3BF5636245C2BB30024D9BF /* ContentView.swift */,
|
||||
E3BF5638245C2BB50024D9BF /* Assets.xcassets */,
|
||||
E3BF563D245C2BB50024D9BF /* Main.storyboard */,
|
||||
E3BF5640245C2BB50024D9BF /* Info.plist */,
|
||||
E3BF5641245C2BB50024D9BF /* KeyboardShortcutsExample.entitlements */,
|
||||
);
|
||||
path = KeyboardShortcutsExample;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E3BF5645245C2D550024D9BF /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
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 */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
OBJ_5 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_16 /* readme.md */,
|
||||
OBJ_6 /* Package.swift */,
|
||||
OBJ_7 /* Sources */,
|
||||
OBJ_11 /* Tests */,
|
||||
E3BF5633245C2BB30024D9BF /* KeyboardShortcutsExample */,
|
||||
OBJ_12 /* Products */,
|
||||
E3BF5645245C2D550024D9BF /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_7 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_8 /* KeyboardShortcuts */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_8 /* KeyboardShortcuts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_9 /* KeyboardShortcuts.swift */,
|
||||
E3BF562C245C29C30024D9BF /* CarbonKeyboardShortcuts.swift */,
|
||||
E38103FD246449180023E9A8 /* Name.swift */,
|
||||
E3BF564B245C34B30024D9BF /* Key.swift */,
|
||||
E3BF562A245C28BD0024D9BF /* Shortcut.swift */,
|
||||
E3BF5628245C24450024D9BF /* RecorderCocoa.swift */,
|
||||
E3BF5626245C23840024D9BF /* Recorder.swift */,
|
||||
OBJ_10 /* util.swift */,
|
||||
);
|
||||
name = KeyboardShortcuts;
|
||||
path = Sources/KeyboardShortcuts;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
E3BF5631245C2BB30024D9BF /* KeyboardShortcutsExample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E3BF5642245C2BB50024D9BF /* Build configuration list for PBXNativeTarget "KeyboardShortcutsExample" */;
|
||||
buildPhases = (
|
||||
E381040124645AC80023E9A8 /* SwiftLint */,
|
||||
E3BF562E245C2BB30024D9BF /* Sources */,
|
||||
E3BF562F245C2BB30024D9BF /* Frameworks */,
|
||||
E3BF5630245C2BB30024D9BF /* Resources */,
|
||||
E3BF564A245C2D550024D9BF /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
E3BF5649245C2D550024D9BF /* PBXTargetDependency */,
|
||||
);
|
||||
name = KeyboardShortcutsExample;
|
||||
productName = KeyboardShortcutsExample;
|
||||
productReference = E3BF5632245C2BB30024D9BF /* KeyboardShortcutsExample.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
"KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_19 /* Build configuration list for PBXNativeTarget "KeyboardShortcuts" */;
|
||||
buildPhases = (
|
||||
E38103FF24645A410023E9A8 /* SwiftLint */,
|
||||
OBJ_22 /* Sources */,
|
||||
OBJ_25 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = KeyboardShortcuts;
|
||||
productName = KeyboardShortcuts;
|
||||
productReference = "KeyboardShortcuts::KeyboardShortcuts::Product" /* KeyboardShortcuts.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
"KeyboardShortcuts::SwiftPMPackageDescription" /* KeyboardShortcutsPackageDescription */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_27 /* Build configuration list for PBXNativeTarget "KeyboardShortcutsPackageDescription" */;
|
||||
buildPhases = (
|
||||
OBJ_30 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = KeyboardShortcutsPackageDescription;
|
||||
productName = KeyboardShortcutsPackageDescription;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
OBJ_1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftMigration = 9999;
|
||||
LastSwiftUpdateCheck = 1140;
|
||||
LastUpgradeCheck = 9999;
|
||||
TargetAttributes = {
|
||||
E3BF5631245C2BB30024D9BF = {
|
||||
CreatedOnToolsVersion = 11.4.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "KeyboardShortcuts" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = OBJ_5;
|
||||
productRefGroup = OBJ_12 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
"KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */,
|
||||
"KeyboardShortcuts::SwiftPMPackageDescription" /* KeyboardShortcutsPackageDescription */,
|
||||
E3BF5631245C2BB30024D9BF /* KeyboardShortcutsExample */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
E3BF5630245C2BB30024D9BF /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E3BF563F245C2BB50024D9BF /* Main.storyboard in Resources */,
|
||||
E3BF5639245C2BB50024D9BF /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
E38103FF24645A410023E9A8 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed\"\nfi\n";
|
||||
};
|
||||
E381040124645AC80023E9A8 /* SwiftLint */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = SwiftLint;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
E3BF562E245C2BB30024D9BF /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E3BF5637245C2BB30024D9BF /* ContentView.swift in Sources */,
|
||||
E3BF5635245C2BB30024D9BF /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_22 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_23 /* KeyboardShortcuts.swift in Sources */,
|
||||
E3BF562D245C29C30024D9BF /* CarbonKeyboardShortcuts.swift in Sources */,
|
||||
OBJ_24 /* util.swift in Sources */,
|
||||
E3BF5629245C24450024D9BF /* RecorderCocoa.swift in Sources */,
|
||||
E3BF564C245C34B30024D9BF /* Key.swift in Sources */,
|
||||
E3BF5627245C23840024D9BF /* Recorder.swift in Sources */,
|
||||
E38103FE246449180023E9A8 /* Name.swift in Sources */,
|
||||
E3BF562B245C28BD0024D9BF /* Shortcut.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_30 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_31 /* Package.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
E3BF5649245C2D550024D9BF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "KeyboardShortcuts::KeyboardShortcuts" /* KeyboardShortcuts */;
|
||||
targetProxy = E3BF5648245C2D550024D9BF /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
E3BF563D245C2BB50024D9BF /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
E3BF563E245C2BB50024D9BF /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
E3BF5643245C2BB50024D9BF /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
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_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
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_DOCUMENTATION_COMMENTS = YES;
|
||||
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;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = KeyboardShortcutsExample/KeyboardShortcutsExample.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = KeyboardShortcutsExample/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E3BF5644245C2BB50024D9BF /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
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_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
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_DOCUMENTATION_COMMENTS = YES;
|
||||
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;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = KeyboardShortcutsExample/KeyboardShortcutsExample.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = KeyboardShortcutsExample/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsExample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_20 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = KeyboardShortcuts.xcodeproj/KeyboardShortcuts_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
|
||||
);
|
||||
MARKETING_VERSION = 0.0.0;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = KeyboardShortcuts;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = KeyboardShortcuts;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_21 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = KeyboardShortcuts.xcodeproj/KeyboardShortcuts_Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
|
||||
);
|
||||
MARKETING_VERSION = 0.0.0;
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = KeyboardShortcuts;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGET_NAME = KeyboardShortcuts;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_28 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_29 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"SWIFT_PACKAGE=1",
|
||||
"DEBUG=1",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
USE_HEADERMAP = NO;
|
||||
VALID_ARCHS = x86_64;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
"SWIFT_PACKAGE=1",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
USE_HEADERMAP = NO;
|
||||
VALID_ARCHS = x86_64;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
E3BF5642245C2BB50024D9BF /* Build configuration list for PBXNativeTarget "KeyboardShortcutsExample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E3BF5643245C2BB50024D9BF /* Debug */,
|
||||
E3BF5644245C2BB50024D9BF /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_19 /* Build configuration list for PBXNativeTarget "KeyboardShortcuts" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_20 /* Debug */,
|
||||
OBJ_21 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_2 /* Build configuration list for PBXProject "KeyboardShortcuts" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_3 /* Debug */,
|
||||
OBJ_4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_27 /* Build configuration list for PBXNativeTarget "KeyboardShortcutsPackageDescription" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_28 /* Debug */,
|
||||
OBJ_29 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = OBJ_1 /* Project object */;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "9999"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "KeyboardShortcuts::KeyboardShortcuts"
|
||||
BuildableName = "KeyboardShortcuts.framework"
|
||||
BlueprintName = "KeyboardShortcuts"
|
||||
ReferencedContainer = "container:KeyboardShortcuts.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</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>
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1140"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E3BF5631245C2BB30024D9BF"
|
||||
BuildableName = "KeyboardShortcutsExample.app"
|
||||
BlueprintName = "KeyboardShortcutsExample"
|
||||
ReferencedContainer = "container:KeyboardShortcuts.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</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">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E3BF5631245C2BB30024D9BF"
|
||||
BuildableName = "KeyboardShortcutsExample.app"
|
||||
BlueprintName = "KeyboardShortcutsExample"
|
||||
ReferencedContainer = "container:KeyboardShortcuts.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "E3BF5631245C2BB30024D9BF"
|
||||
BuildableName = "KeyboardShortcutsExample.app"
|
||||
BlueprintName = "KeyboardShortcutsExample"
|
||||
ReferencedContainer = "container:KeyboardShortcuts.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,29 @@
|
|||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
@NSApplicationMain
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
var window: NSWindow!
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
let contentView = ContentView()
|
||||
|
||||
window = NSWindow(
|
||||
contentRect: CGRect(x: 0, y: 0, width: 480, height: 300),
|
||||
styleMask: [
|
||||
.titled,
|
||||
.closable,
|
||||
.miniaturizable,
|
||||
.fullSizeContentView
|
||||
],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
||||
window.title = "KeyboardShortcuts Example"
|
||||
window.center()
|
||||
window.setFrameAutosaveName("Main Window")
|
||||
window.contentView = NSHostingView(rootView: contentView)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="KeyboardShortcutsExample" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="KeyboardShortcutsExample" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About KeyboardShortcutsExample" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide KeyboardShortcutsExample" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit KeyboardShortcutsExample" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||
<connections>
|
||||
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||
<connections>
|
||||
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||
<connections>
|
||||
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||
<connections>
|
||||
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||
<menuItem title="Find" id="4EN-yA-p0u">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||
<items>
|
||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||
<connections>
|
||||
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||
<connections>
|
||||
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||
<items>
|
||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||
<connections>
|
||||
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||
<connections>
|
||||
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||
<items>
|
||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||
<items>
|
||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||
<items>
|
||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
<items>
|
||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="toggleFullScreen:" target="Ady-hI-5gd" id="dU3-MA-1Rq"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="KeyboardShortcutsExample Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="KeyboardShortcutsExample" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,52 @@
|
|||
import SwiftUI
|
||||
import KeyboardShortcuts
|
||||
|
||||
extension KeyboardShortcuts.Name {
|
||||
static let testShortcut1 = Name("testShortcut1")
|
||||
static let testShortcut2 = Name("testShortcut2")
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var isPressed1 = false
|
||||
@State private var isPressed2 = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
KeyboardShortcuts.Recorder(for: .testShortcut1)
|
||||
Text("Is pressed? \(isPressed1 ? "Yes" : "No")")
|
||||
.frame(width: 100, alignment: .leading)
|
||||
}
|
||||
HStack {
|
||||
KeyboardShortcuts.Recorder(for: .testShortcut2)
|
||||
Text("Is pressed? \(isPressed2 ? "Yes" : "No")")
|
||||
.frame(width: 100, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 300)
|
||||
.padding(60)
|
||||
.onAppear {
|
||||
KeyboardShortcuts.onKeyDown(for: .testShortcut1) {
|
||||
self.isPressed1 = true
|
||||
}
|
||||
|
||||
KeyboardShortcuts.onKeyUp(for: .testShortcut1) {
|
||||
self.isPressed1 = false
|
||||
}
|
||||
|
||||
KeyboardShortcuts.onKeyDown(for: .testShortcut2) {
|
||||
self.isPressed2 = true
|
||||
}
|
||||
|
||||
KeyboardShortcuts.onKeyUp(for: .testShortcut2) {
|
||||
self.isPressed2 = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?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>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<true/>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,10 @@
|
|||
<?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>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:5.2
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "KeyboardShortcuts",
|
||||
platforms: [
|
||||
.macOS(.v10_11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "KeyboardShortcuts",
|
||||
targets: [
|
||||
"KeyboardShortcuts"
|
||||
]
|
||||
)
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "KeyboardShortcuts"
|
||||
)
|
||||
]
|
||||
)
|
|
@ -0,0 +1,181 @@
|
|||
import Carbon.HIToolbox
|
||||
|
||||
private func carbonKeyboardShortcutsEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
CarbonKeyboardShortcuts.handleEvent(event)
|
||||
}
|
||||
|
||||
final class CarbonKeyboardShortcuts {
|
||||
private final class HotKey {
|
||||
let shortcut: KeyboardShortcuts.Shortcut
|
||||
let carbonHotKeyId: Int
|
||||
let carbonHotKey: EventHotKeyRef
|
||||
let onKeyDown: (KeyboardShortcuts.Shortcut) -> Void
|
||||
let onKeyUp: (KeyboardShortcuts.Shortcut) -> Void
|
||||
|
||||
init(
|
||||
shortcut: KeyboardShortcuts.Shortcut,
|
||||
carbonHotKeyID: Int,
|
||||
carbonHotKey: EventHotKeyRef,
|
||||
onKeyDown: @escaping (KeyboardShortcuts.Shortcut) -> Void,
|
||||
onKeyUp: @escaping (KeyboardShortcuts.Shortcut) -> Void
|
||||
) {
|
||||
self.shortcut = shortcut
|
||||
self.carbonHotKeyId = carbonHotKeyID
|
||||
self.carbonHotKey = carbonHotKey
|
||||
self.onKeyDown = onKeyDown
|
||||
self.onKeyUp = onKeyUp
|
||||
}
|
||||
}
|
||||
|
||||
private static var hotKeys = [Int: HotKey]()
|
||||
|
||||
// `SSKS` is just short for `Sindre Sorhus Keyboard Shortcuts`.
|
||||
private static let hotKeySignature = UTGetOSTypeFromString("SSKS" as CFString)
|
||||
|
||||
private static var hotKeyId = 0
|
||||
private static var eventHandler: EventHandlerRef?
|
||||
|
||||
private static func setUpEventHandlerIfNeeded() {
|
||||
guard
|
||||
eventHandler == nil,
|
||||
let dispatcher = GetEventDispatcherTarget()
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let eventSpecs = [
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
|
||||
]
|
||||
|
||||
InstallEventHandler(
|
||||
dispatcher,
|
||||
carbonKeyboardShortcutsEventHandler,
|
||||
eventSpecs.count,
|
||||
eventSpecs,
|
||||
nil,
|
||||
&eventHandler
|
||||
)
|
||||
}
|
||||
|
||||
static func register(
|
||||
_ shortcut: KeyboardShortcuts.Shortcut,
|
||||
onKeyDown: @escaping (KeyboardShortcuts.Shortcut) -> Void,
|
||||
onKeyUp: @escaping (KeyboardShortcuts.Shortcut) -> Void
|
||||
) {
|
||||
hotKeyId += 1
|
||||
|
||||
var eventHotKey: EventHotKeyRef?
|
||||
let registerError = RegisterEventHotKey(
|
||||
UInt32(shortcut.carbonKeyCode),
|
||||
UInt32(shortcut.carbonModifiers),
|
||||
EventHotKeyID(signature: hotKeySignature, id: UInt32(hotKeyId)),
|
||||
GetEventDispatcherTarget(),
|
||||
0,
|
||||
&eventHotKey
|
||||
)
|
||||
|
||||
guard
|
||||
registerError == noErr,
|
||||
let carbonHotKey = eventHotKey
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
hotKeys[hotKeyId] = HotKey(
|
||||
shortcut: shortcut,
|
||||
carbonHotKeyID: hotKeyId,
|
||||
carbonHotKey: carbonHotKey,
|
||||
onKeyDown: onKeyDown,
|
||||
onKeyUp: onKeyUp
|
||||
)
|
||||
|
||||
setUpEventHandlerIfNeeded()
|
||||
}
|
||||
|
||||
private static func unregisterHotKey(_ hotKey: HotKey) {
|
||||
UnregisterEventHotKey(hotKey.carbonHotKey)
|
||||
hotKeys.removeValue(forKey: hotKey.carbonHotKeyId)
|
||||
}
|
||||
|
||||
static func unregister(_ shortcut: KeyboardShortcuts.Shortcut) {
|
||||
for hotKey in hotKeys.values where hotKey.shortcut == shortcut {
|
||||
unregisterHotKey(hotKey)
|
||||
}
|
||||
}
|
||||
|
||||
static func unregisterAll() {
|
||||
for hotKey in hotKeys.values {
|
||||
unregisterHotKey(hotKey)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static func handleEvent(_ event: EventRef?) -> OSStatus {
|
||||
guard let event = event else {
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
|
||||
var eventHotKeyId = EventHotKeyID()
|
||||
let error = GetEventParameter(
|
||||
event,
|
||||
UInt32(kEventParamDirectObject),
|
||||
UInt32(typeEventHotKeyID),
|
||||
nil,
|
||||
MemoryLayout<EventHotKeyID>.size,
|
||||
nil,
|
||||
&eventHotKeyId
|
||||
)
|
||||
|
||||
guard error == noErr else {
|
||||
return error
|
||||
}
|
||||
|
||||
guard
|
||||
eventHotKeyId.signature == hotKeySignature,
|
||||
let hotKey = hotKeys[Int(eventHotKeyId.id)]
|
||||
else {
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
|
||||
switch Int(GetEventKind(event)) {
|
||||
case kEventHotKeyPressed:
|
||||
hotKey.onKeyDown(hotKey.shortcut)
|
||||
return noErr
|
||||
case kEventHotKeyReleased:
|
||||
hotKey.onKeyUp(hotKey.shortcut)
|
||||
return noErr
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
}
|
||||
|
||||
extension CarbonKeyboardShortcuts {
|
||||
static var system: [KeyboardShortcuts.Shortcut] {
|
||||
var shortcutsUnmanaged: Unmanaged<CFArray>?
|
||||
guard
|
||||
CopySymbolicHotKeys(&shortcutsUnmanaged) == noErr,
|
||||
let shortcuts = shortcutsUnmanaged?.takeRetainedValue() as? [[String: Any]]
|
||||
else {
|
||||
assertionFailure("Could not get system keyboard shortcuts")
|
||||
return []
|
||||
}
|
||||
|
||||
return shortcuts.compactMap {
|
||||
guard
|
||||
($0[kHISymbolicHotKeyEnabled] as? Bool) == true,
|
||||
let carbonKeyCode = $0[kHISymbolicHotKeyCode] as? Int,
|
||||
let carbonModifiers = $0[kHISymbolicHotKeyModifiers] as? Int
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return KeyboardShortcuts.Shortcut(
|
||||
carbonKeyCode: carbonKeyCode,
|
||||
carbonModifiers: carbonModifiers
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,743 @@
|
|||
import Cocoa
|
||||
import Carbon.HIToolbox
|
||||
|
||||
extension KeyboardShortcuts {
|
||||
// swiftlint:disable identifier_name
|
||||
/// Represents a key on the keyboard.
|
||||
public enum Key: RawRepresentable {
|
||||
case a
|
||||
case b
|
||||
case c
|
||||
case d
|
||||
case e
|
||||
case f
|
||||
case g
|
||||
case h
|
||||
case i
|
||||
case j
|
||||
case k
|
||||
case l
|
||||
case m
|
||||
case n
|
||||
case o
|
||||
case p
|
||||
case q
|
||||
case r
|
||||
case s
|
||||
case t
|
||||
case u
|
||||
case v
|
||||
case w
|
||||
case x
|
||||
case y
|
||||
case z
|
||||
|
||||
case zero
|
||||
case one
|
||||
case two
|
||||
case three
|
||||
case four
|
||||
case five
|
||||
case six
|
||||
case seven
|
||||
case eight
|
||||
case nine
|
||||
|
||||
case backslash
|
||||
case backtick
|
||||
case comma
|
||||
case equal
|
||||
case escape
|
||||
case leftBracket
|
||||
case minus
|
||||
case period
|
||||
case quote
|
||||
case rightBracket
|
||||
case semicolon
|
||||
case slash
|
||||
case space
|
||||
|
||||
// `NSEvent.SpecialKey`
|
||||
case backTab
|
||||
case backspace
|
||||
case begin
|
||||
case `break`
|
||||
case carriageReturn
|
||||
case clearDisplay
|
||||
case clearLine
|
||||
case delete
|
||||
case deleteCharacter
|
||||
case deleteForward
|
||||
case deleteLine
|
||||
case downArrow
|
||||
case end
|
||||
case enter
|
||||
case execute
|
||||
case f1
|
||||
case f2
|
||||
case f3
|
||||
case f4
|
||||
case f5
|
||||
case f6
|
||||
case f7
|
||||
case f8
|
||||
case f9
|
||||
case f10
|
||||
case f11
|
||||
case f12
|
||||
case f13
|
||||
case f14
|
||||
case f15
|
||||
case f16
|
||||
case f17
|
||||
case f18
|
||||
case f19
|
||||
case f20
|
||||
case f21
|
||||
case f22
|
||||
case f23
|
||||
case f24
|
||||
case f25
|
||||
case f26
|
||||
case f27
|
||||
case f28
|
||||
case f29
|
||||
case f30
|
||||
case f31
|
||||
case f32
|
||||
case f33
|
||||
case f34
|
||||
case f35
|
||||
case find
|
||||
case formFeed
|
||||
case help
|
||||
case home
|
||||
case insert
|
||||
case insertCharacter
|
||||
case insertLine
|
||||
case leftArrow
|
||||
case lineSeparator
|
||||
case menu
|
||||
case modeSwitch
|
||||
case newline
|
||||
case next
|
||||
case pageDown
|
||||
case pageUp
|
||||
case paragraphSeparator
|
||||
case pause
|
||||
case prev
|
||||
case print
|
||||
case printScreen
|
||||
case redo
|
||||
case reset
|
||||
case rightArrow
|
||||
case scrollLock
|
||||
case select
|
||||
case stop
|
||||
case sysReq
|
||||
case system
|
||||
case tab
|
||||
case undo
|
||||
case upArrow
|
||||
case user
|
||||
// swiftlint:enable identifier_name
|
||||
|
||||
/// Create a `Key` from a key code.
|
||||
public init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case kVK_ANSI_A:
|
||||
self = .a
|
||||
case kVK_ANSI_B:
|
||||
self = .b
|
||||
case kVK_ANSI_C:
|
||||
self = .c
|
||||
case kVK_ANSI_D:
|
||||
self = .d
|
||||
case kVK_ANSI_E:
|
||||
self = .e
|
||||
case kVK_ANSI_F:
|
||||
self = .f
|
||||
case kVK_ANSI_G:
|
||||
self = .g
|
||||
case kVK_ANSI_H:
|
||||
self = .h
|
||||
case kVK_ANSI_I:
|
||||
self = .i
|
||||
case kVK_ANSI_J:
|
||||
self = .j
|
||||
case kVK_ANSI_K:
|
||||
self = .k
|
||||
case kVK_ANSI_L:
|
||||
self = .l
|
||||
case kVK_ANSI_M:
|
||||
self = .m
|
||||
case kVK_ANSI_N:
|
||||
self = .n
|
||||
case kVK_ANSI_O:
|
||||
self = .o
|
||||
case kVK_ANSI_P:
|
||||
self = .p
|
||||
case kVK_ANSI_Q:
|
||||
self = .q
|
||||
case kVK_ANSI_R:
|
||||
self = .r
|
||||
case kVK_ANSI_S:
|
||||
self = .s
|
||||
case kVK_ANSI_T:
|
||||
self = .t
|
||||
case kVK_ANSI_U:
|
||||
self = .u
|
||||
case kVK_ANSI_V:
|
||||
self = .v
|
||||
case kVK_ANSI_W:
|
||||
self = .w
|
||||
case kVK_ANSI_X:
|
||||
self = .x
|
||||
case kVK_ANSI_Y:
|
||||
self = .y
|
||||
case kVK_ANSI_Z:
|
||||
self = .z
|
||||
case kVK_ANSI_0:
|
||||
self = .zero
|
||||
case kVK_ANSI_1:
|
||||
self = .one
|
||||
case kVK_ANSI_2:
|
||||
self = .two
|
||||
case kVK_ANSI_3:
|
||||
self = .three
|
||||
case kVK_ANSI_4:
|
||||
self = .four
|
||||
case kVK_ANSI_5:
|
||||
self = .five
|
||||
case kVK_ANSI_6:
|
||||
self = .six
|
||||
case kVK_ANSI_7:
|
||||
self = .seven
|
||||
case kVK_ANSI_8:
|
||||
self = .eight
|
||||
case kVK_ANSI_9:
|
||||
self = .nine
|
||||
case kVK_ANSI_Backslash:
|
||||
self = .backslash
|
||||
case kVK_ANSI_Grave:
|
||||
self = .backtick
|
||||
case kVK_ANSI_Comma:
|
||||
self = .comma
|
||||
case kVK_ANSI_Equal:
|
||||
self = .equal
|
||||
case kVK_Escape:
|
||||
self = .escape
|
||||
case kVK_ANSI_LeftBracket:
|
||||
self = .leftBracket
|
||||
case kVK_ANSI_Minus:
|
||||
self = .minus
|
||||
case kVK_ANSI_Period:
|
||||
self = .period
|
||||
case kVK_ANSI_Quote:
|
||||
self = .quote
|
||||
case kVK_ANSI_RightBracket:
|
||||
self = .rightBracket
|
||||
case kVK_ANSI_Semicolon:
|
||||
self = .semicolon
|
||||
case kVK_ANSI_Slash:
|
||||
self = .slash
|
||||
case kVK_Space:
|
||||
self = .space
|
||||
default:
|
||||
self.init(specialKey: .init(rawValue: rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
private init?(specialKey: NSEvent.SpecialKey) {
|
||||
switch specialKey {
|
||||
case .backTab:
|
||||
self = .backTab
|
||||
case .backspace:
|
||||
self = .backspace
|
||||
case .begin:
|
||||
self = .begin
|
||||
case .break:
|
||||
self = .break
|
||||
case .carriageReturn:
|
||||
self = .carriageReturn
|
||||
case .clearDisplay:
|
||||
self = .clearDisplay
|
||||
case .clearLine:
|
||||
self = .clearLine
|
||||
case .delete:
|
||||
self = .delete
|
||||
case .deleteCharacter:
|
||||
self = .deleteCharacter
|
||||
case .deleteForward:
|
||||
self = .deleteForward
|
||||
case .deleteLine:
|
||||
self = .deleteLine
|
||||
case .downArrow:
|
||||
self = .downArrow
|
||||
case .end:
|
||||
self = .end
|
||||
case .enter:
|
||||
self = .enter
|
||||
case .execute:
|
||||
self = .execute
|
||||
case .f1:
|
||||
self = .f1
|
||||
case .f2:
|
||||
self = .f2
|
||||
case .f3:
|
||||
self = .f3
|
||||
case .f4:
|
||||
self = .f4
|
||||
case .f5:
|
||||
self = .f5
|
||||
case .f6:
|
||||
self = .f6
|
||||
case .f7:
|
||||
self = .f7
|
||||
case .f8:
|
||||
self = .f8
|
||||
case .f9:
|
||||
self = .f9
|
||||
case .f10:
|
||||
self = .f10
|
||||
case .f11:
|
||||
self = .f11
|
||||
case .f12:
|
||||
self = .f12
|
||||
case .f13:
|
||||
self = .f13
|
||||
case .f14:
|
||||
self = .f14
|
||||
case .f15:
|
||||
self = .f15
|
||||
case .f16:
|
||||
self = .f16
|
||||
case .f17:
|
||||
self = .f17
|
||||
case .f18:
|
||||
self = .f18
|
||||
case .f19:
|
||||
self = .f19
|
||||
case .f20:
|
||||
self = .f20
|
||||
case .f21:
|
||||
self = .f21
|
||||
case .f22:
|
||||
self = .f22
|
||||
case .f23:
|
||||
self = .f23
|
||||
case .f24:
|
||||
self = .f24
|
||||
case .f25:
|
||||
self = .f25
|
||||
case .f26:
|
||||
self = .f26
|
||||
case .f27:
|
||||
self = .f27
|
||||
case .f28:
|
||||
self = .f28
|
||||
case .f29:
|
||||
self = .f29
|
||||
case .f30:
|
||||
self = .f30
|
||||
case .f31:
|
||||
self = .f31
|
||||
case .f32:
|
||||
self = .f32
|
||||
case .f33:
|
||||
self = .f33
|
||||
case .f34:
|
||||
self = .f34
|
||||
case .f35:
|
||||
self = .f35
|
||||
case .find:
|
||||
self = .find
|
||||
case .formFeed:
|
||||
self = .formFeed
|
||||
case .help:
|
||||
self = .help
|
||||
case .home:
|
||||
self = .home
|
||||
case .insert:
|
||||
self = .insert
|
||||
case .insertCharacter:
|
||||
self = .insertCharacter
|
||||
case .insertLine:
|
||||
self = .insertLine
|
||||
case .leftArrow:
|
||||
self = .leftArrow
|
||||
case .lineSeparator:
|
||||
self = .lineSeparator
|
||||
case .menu:
|
||||
self = .menu
|
||||
case .modeSwitch:
|
||||
self = .modeSwitch
|
||||
case .newline:
|
||||
self = .newline
|
||||
case .next:
|
||||
self = .next
|
||||
case .pageDown:
|
||||
self = .pageDown
|
||||
case .pageUp:
|
||||
self = .pageUp
|
||||
case .paragraphSeparator:
|
||||
self = .paragraphSeparator
|
||||
case .pause:
|
||||
self = .pause
|
||||
case .prev:
|
||||
self = .prev
|
||||
case .print:
|
||||
self = .print
|
||||
case .printScreen:
|
||||
self = .printScreen
|
||||
case .redo:
|
||||
self = .redo
|
||||
case .reset:
|
||||
self = .reset
|
||||
case .rightArrow:
|
||||
self = .rightArrow
|
||||
case .scrollLock:
|
||||
self = .scrollLock
|
||||
case .select:
|
||||
self = .select
|
||||
case .stop:
|
||||
self = .stop
|
||||
case .sysReq:
|
||||
self = .sysReq
|
||||
case .system:
|
||||
self = .system
|
||||
case .tab:
|
||||
self = .tab
|
||||
case .undo:
|
||||
self = .undo
|
||||
case .upArrow:
|
||||
self = .upArrow
|
||||
case .user:
|
||||
self = .user
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var specialKey: NSEvent.SpecialKey? {
|
||||
switch self {
|
||||
case .backTab:
|
||||
return .backTab
|
||||
case .backspace:
|
||||
return .backspace
|
||||
case .begin:
|
||||
return .begin
|
||||
case .break:
|
||||
return .break
|
||||
case .carriageReturn:
|
||||
return .carriageReturn
|
||||
case .clearDisplay:
|
||||
return .clearDisplay
|
||||
case .clearLine:
|
||||
return .clearLine
|
||||
case .delete:
|
||||
return .delete
|
||||
case .deleteCharacter:
|
||||
return .deleteCharacter
|
||||
case .deleteForward:
|
||||
return .deleteForward
|
||||
case .deleteLine:
|
||||
return .deleteLine
|
||||
case .downArrow:
|
||||
return .downArrow
|
||||
case .end:
|
||||
return .end
|
||||
case .enter:
|
||||
return .enter
|
||||
case .execute:
|
||||
return .execute
|
||||
case .f1:
|
||||
return .f1
|
||||
case .f2:
|
||||
return .f2
|
||||
case .f3:
|
||||
return .f3
|
||||
case .f4:
|
||||
return .f4
|
||||
case .f5:
|
||||
return .f5
|
||||
case .f6:
|
||||
return .f6
|
||||
case .f7:
|
||||
return .f7
|
||||
case .f8:
|
||||
return .f8
|
||||
case .f9:
|
||||
return .f9
|
||||
case .f10:
|
||||
return .f10
|
||||
case .f11:
|
||||
return .f11
|
||||
case .f12:
|
||||
return .f12
|
||||
case .f13:
|
||||
return .f13
|
||||
case .f14:
|
||||
return .f14
|
||||
case .f15:
|
||||
return .f15
|
||||
case .f16:
|
||||
return .f16
|
||||
case .f17:
|
||||
return .f17
|
||||
case .f18:
|
||||
return .f18
|
||||
case .f19:
|
||||
return .f19
|
||||
case .f20:
|
||||
return .f20
|
||||
case .f21:
|
||||
return .f21
|
||||
case .f22:
|
||||
return .f22
|
||||
case .f23:
|
||||
return .f23
|
||||
case .f24:
|
||||
return .f24
|
||||
case .f25:
|
||||
return .f25
|
||||
case .f26:
|
||||
return .f26
|
||||
case .f27:
|
||||
return .f27
|
||||
case .f28:
|
||||
return .f28
|
||||
case .f29:
|
||||
return .f29
|
||||
case .f30:
|
||||
return .f30
|
||||
case .f31:
|
||||
return .f31
|
||||
case .f32:
|
||||
return .f32
|
||||
case .f33:
|
||||
return .f33
|
||||
case .f34:
|
||||
return .f34
|
||||
case .f35:
|
||||
return .f35
|
||||
case .find:
|
||||
return .find
|
||||
case .formFeed:
|
||||
return .formFeed
|
||||
case .help:
|
||||
return .help
|
||||
case .home:
|
||||
return .home
|
||||
case .insert:
|
||||
return .insert
|
||||
case .insertCharacter:
|
||||
return .insertCharacter
|
||||
case .insertLine:
|
||||
return .insertLine
|
||||
case .leftArrow:
|
||||
return .leftArrow
|
||||
case .lineSeparator:
|
||||
return .lineSeparator
|
||||
case .menu:
|
||||
return .menu
|
||||
case .modeSwitch:
|
||||
return .modeSwitch
|
||||
case .newline:
|
||||
return .newline
|
||||
case .next:
|
||||
return .next
|
||||
case .pageDown:
|
||||
return .pageDown
|
||||
case .pageUp:
|
||||
return .pageUp
|
||||
case .paragraphSeparator:
|
||||
return .paragraphSeparator
|
||||
case .pause:
|
||||
return .pause
|
||||
case .prev:
|
||||
return .prev
|
||||
case .print:
|
||||
return .print
|
||||
case .printScreen:
|
||||
return .printScreen
|
||||
case .redo:
|
||||
return .redo
|
||||
case .reset:
|
||||
return .reset
|
||||
case .rightArrow:
|
||||
return .rightArrow
|
||||
case .scrollLock:
|
||||
return .scrollLock
|
||||
case .select:
|
||||
return .select
|
||||
case .stop:
|
||||
return .stop
|
||||
case .sysReq:
|
||||
return .sysReq
|
||||
case .system:
|
||||
return .system
|
||||
case .tab:
|
||||
return .tab
|
||||
case .undo:
|
||||
return .undo
|
||||
case .upArrow:
|
||||
return .upArrow
|
||||
case .user:
|
||||
return .user
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// The raw key code.
|
||||
public var rawValue: Int {
|
||||
switch self {
|
||||
case .a:
|
||||
return kVK_ANSI_A
|
||||
case .b:
|
||||
return kVK_ANSI_B
|
||||
case .c:
|
||||
return kVK_ANSI_C
|
||||
case .d:
|
||||
return kVK_ANSI_D
|
||||
case .e:
|
||||
return kVK_ANSI_E
|
||||
case .f:
|
||||
return kVK_ANSI_F
|
||||
case .g:
|
||||
return kVK_ANSI_G
|
||||
case .h:
|
||||
return kVK_ANSI_H
|
||||
case .i:
|
||||
return kVK_ANSI_I
|
||||
case .j:
|
||||
return kVK_ANSI_J
|
||||
case .k:
|
||||
return kVK_ANSI_K
|
||||
case .l:
|
||||
return kVK_ANSI_L
|
||||
case .m:
|
||||
return kVK_ANSI_M
|
||||
case .n:
|
||||
return kVK_ANSI_N
|
||||
case .o:
|
||||
return kVK_ANSI_O
|
||||
case .p:
|
||||
return kVK_ANSI_P
|
||||
case .q:
|
||||
return kVK_ANSI_Q
|
||||
case .r:
|
||||
return kVK_ANSI_R
|
||||
case .s:
|
||||
return kVK_ANSI_S
|
||||
case .t:
|
||||
return kVK_ANSI_T
|
||||
case .u:
|
||||
return kVK_ANSI_U
|
||||
case .v:
|
||||
return kVK_ANSI_V
|
||||
case .w:
|
||||
return kVK_ANSI_W
|
||||
case .x:
|
||||
return kVK_ANSI_X
|
||||
case .y:
|
||||
return kVK_ANSI_Y
|
||||
case .z:
|
||||
return kVK_ANSI_Z
|
||||
case .zero:
|
||||
return kVK_ANSI_0
|
||||
case .one:
|
||||
return kVK_ANSI_1
|
||||
case .two:
|
||||
return kVK_ANSI_2
|
||||
case .three:
|
||||
return kVK_ANSI_3
|
||||
case .four:
|
||||
return kVK_ANSI_4
|
||||
case .five:
|
||||
return kVK_ANSI_5
|
||||
case .six:
|
||||
return kVK_ANSI_6
|
||||
case .seven:
|
||||
return kVK_ANSI_7
|
||||
case .eight:
|
||||
return kVK_ANSI_8
|
||||
case .nine:
|
||||
return kVK_ANSI_9
|
||||
case .backslash:
|
||||
return kVK_ANSI_Backslash
|
||||
case .backtick:
|
||||
return kVK_ANSI_Grave
|
||||
case .comma:
|
||||
return kVK_ANSI_Comma
|
||||
case .equal:
|
||||
return kVK_ANSI_Equal
|
||||
case .escape:
|
||||
return kVK_Escape
|
||||
case .leftBracket:
|
||||
return kVK_ANSI_LeftBracket
|
||||
case .minus:
|
||||
return kVK_ANSI_Minus
|
||||
case .period:
|
||||
return kVK_ANSI_Period
|
||||
case .quote:
|
||||
return kVK_ANSI_Quote
|
||||
case .rightBracket:
|
||||
return kVK_ANSI_RightBracket
|
||||
case .semicolon:
|
||||
return kVK_ANSI_Semicolon
|
||||
case .slash:
|
||||
return kVK_ANSI_Slash
|
||||
case .space:
|
||||
return kVK_Space
|
||||
case .backTab, .backspace, .begin, .break, .carriageReturn, .clearDisplay, .clearLine, .delete, .deleteCharacter, .deleteForward, .deleteLine, .downArrow, .end, .enter, .execute, .f1, .f2, .f3, .f4, .f5, .f6, .f7, .f8, .f9, .f10, .f11, .f12, .f13, .f14, .f15, .f16, .f17, .f18, .f19, .f20, .f21, .f22, .f23, .f24, .f25, .f26, .f27, .f28, .f29, .f30, .f31, .f32, .f33, .f34, .f35, .find, .formFeed, .help, .home, .insert, .insertCharacter, .insertLine, .leftArrow, .lineSeparator, .menu, .modeSwitch, .newline, .next, .pageDown, .pageUp, .paragraphSeparator, .pause, .prev, .print, .printScreen, .redo, .reset, .rightArrow, .scrollLock, .select, .stop, .sysReq, .system, .tab, .undo, .upArrow, .user:
|
||||
return specialKey?.rawValue ?? 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyboardShortcuts.Key {
|
||||
/// All the function keys.
|
||||
static let functionKeys: Set<Self> = [
|
||||
.f1,
|
||||
.f2,
|
||||
.f3,
|
||||
.f4,
|
||||
.f5,
|
||||
.f6,
|
||||
.f7,
|
||||
.f8,
|
||||
.f9,
|
||||
.f10,
|
||||
.f11,
|
||||
.f12,
|
||||
.f13,
|
||||
.f14,
|
||||
.f15,
|
||||
.f16,
|
||||
.f17,
|
||||
.f18,
|
||||
.f19,
|
||||
.f20,
|
||||
.f21,
|
||||
.f22,
|
||||
.f23,
|
||||
.f24,
|
||||
.f25,
|
||||
.f26,
|
||||
.f27,
|
||||
.f28,
|
||||
.f29,
|
||||
.f30,
|
||||
.f31,
|
||||
.f32,
|
||||
.f33,
|
||||
.f34,
|
||||
.f35
|
||||
]
|
||||
|
||||
/// Returns true if the key is a function key. For example, `F1`.
|
||||
var isFunctionKey: Bool { Self.functionKeys.contains(self) }
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
import Cocoa
|
||||
|
||||
/**
|
||||
Global keyboard shortcuts for your macOS app.
|
||||
*/
|
||||
public final class KeyboardShortcuts {
|
||||
/// :nodoc:
|
||||
public typealias KeyAction = () -> Void
|
||||
|
||||
private static var registeredShortcuts = Set<Shortcut>()
|
||||
|
||||
// Not currently used. For the future.
|
||||
private static var keyDownHandlers = [Shortcut: [KeyAction]]()
|
||||
private static var keyUpHandlers = [Shortcut: [KeyAction]]()
|
||||
|
||||
private static var userDefaultsKeyDownHandlers = [Name: [KeyAction]]()
|
||||
private static var userDefaultsKeyUpHandlers = [Name: [KeyAction]]()
|
||||
|
||||
private static func register(_ shortcut: Shortcut) {
|
||||
guard !registeredShortcuts.contains(shortcut) else {
|
||||
return
|
||||
}
|
||||
|
||||
CarbonKeyboardShortcuts.register(
|
||||
shortcut,
|
||||
onKeyDown: handleOnKeyDown,
|
||||
onKeyUp: handleOnKeyUp
|
||||
)
|
||||
|
||||
registeredShortcuts.insert(shortcut)
|
||||
}
|
||||
|
||||
private static func unregister(_ shortcut: Shortcut) {
|
||||
CarbonKeyboardShortcuts.unregister(shortcut)
|
||||
registeredShortcuts.remove(shortcut)
|
||||
}
|
||||
|
||||
// TODO: Doc comment and make this public.
|
||||
static func unregisterAll() {
|
||||
CarbonKeyboardShortcuts.unregisterAll()
|
||||
registeredShortcuts.removeAll()
|
||||
|
||||
// TODO: Should remove user defaults too.
|
||||
}
|
||||
|
||||
// TODO: Also add `.isEnabled(_ name: Name)`.
|
||||
static func disable(_ name: Name) {
|
||||
guard let shortcut = userDefaultsGet(name: name) else {
|
||||
return
|
||||
}
|
||||
|
||||
unregister(shortcut)
|
||||
}
|
||||
|
||||
static func enable(_ name: Name) {
|
||||
guard let shortcut = userDefaultsGet(name: name) else {
|
||||
return
|
||||
}
|
||||
|
||||
register(shortcut)
|
||||
}
|
||||
|
||||
private static func handleOnKeyDown(_ shortcut: Shortcut) {
|
||||
if let handlers = keyDownHandlers[shortcut] {
|
||||
for handler in handlers {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
for (name, handlers) in userDefaultsKeyDownHandlers {
|
||||
guard userDefaultsGet(name: name) == shortcut else {
|
||||
continue
|
||||
}
|
||||
|
||||
for handler in handlers {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func handleOnKeyUp(_ shortcut: Shortcut) {
|
||||
if let handlers = keyUpHandlers[shortcut] {
|
||||
for handler in handlers {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
|
||||
for (name, handlers) in userDefaultsKeyUpHandlers {
|
||||
guard userDefaultsGet(name: name) == shortcut else {
|
||||
continue
|
||||
}
|
||||
|
||||
for handler in handlers {
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Listen to the keyboard shortcut with the given name being pressed.
|
||||
|
||||
You can register multiple listeners.
|
||||
|
||||
You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.
|
||||
|
||||
```
|
||||
import Cocoa
|
||||
import KeyboardShortcuts
|
||||
|
||||
@NSApplicationMain
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
KeyboardShortcuts.onKeyDown(for: .toggleUnicornMode) {
|
||||
self.isUnicornMode.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public static func onKeyDown(for name: Name, action: @escaping KeyAction) {
|
||||
if userDefaultsKeyDownHandlers[name] == nil {
|
||||
userDefaultsKeyDownHandlers[name] = []
|
||||
}
|
||||
|
||||
userDefaultsKeyDownHandlers[name]?.append(action)
|
||||
|
||||
// If the keyboard shortcut already exist, we register it.
|
||||
if let shortcut = userDefaultsGet(name: name) {
|
||||
register(shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Listen to the keyboard shortcut with the given name being pressed.
|
||||
|
||||
You can register multiple listeners.
|
||||
|
||||
You can safely call this even if the user has not yet set a keyboard shortcut. It will just be inactive until they do.
|
||||
|
||||
```
|
||||
import Cocoa
|
||||
import KeyboardShortcuts
|
||||
|
||||
@NSApplicationMain
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
KeyboardShortcuts.onKeyUp(for: .toggleUnicornMode) {
|
||||
self.isUnicornMode.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public static func onKeyUp(for name: Name, action: @escaping KeyAction) {
|
||||
if userDefaultsKeyUpHandlers[name] == nil {
|
||||
userDefaultsKeyUpHandlers[name] = []
|
||||
}
|
||||
|
||||
userDefaultsKeyUpHandlers[name]?.append(action)
|
||||
|
||||
// If the keyboard shortcut already exist, we register it.
|
||||
if let shortcut = userDefaultsGet(name: name) {
|
||||
register(shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
private static let userDefaultsPrefix = "KeyboardShortcuts_"
|
||||
|
||||
private static func userDefaultsKey(for shortcutName: Name) -> String { "\(userDefaultsPrefix)\(shortcutName.rawValue)"
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
UserDefaults.standard.set(encoded, forKey: userDefaultsKey(for: name))
|
||||
register(shortcut)
|
||||
}
|
||||
|
||||
static func userDefaultsRemove(name: Name) {
|
||||
guard let shortcut = userDefaultsGet(name: name) else {
|
||||
return
|
||||
}
|
||||
|
||||
UserDefaults.standard.removeObject(forKey: userDefaultsKey(for: name))
|
||||
unregister(shortcut)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
extension KeyboardShortcuts {
|
||||
/**
|
||||
The strongly-typed name of the keyboard shortcut.
|
||||
|
||||
After registering it, you can use it in, for example, `KeyboardShortcut.Recorder` and `KeyboardShortcut.onKeyUp()`.
|
||||
|
||||
```
|
||||
import KeyboardShortcuts
|
||||
|
||||
extension KeyboardShortcuts.Name {
|
||||
static let toggleUnicornMode = Name("toggleUnicornMode")
|
||||
}
|
||||
```
|
||||
*/
|
||||
public struct Name: Hashable {
|
||||
// This is to allow `extension KeyboardShortcuts.Name { static let x = Name("x") }`.
|
||||
/// :nodoc:
|
||||
public typealias Name = KeyboardShortcuts.Name
|
||||
|
||||
public let rawValue: String
|
||||
|
||||
public init(_ name: String) {
|
||||
self.rawValue = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyboardShortcuts.Name: RawRepresentable {
|
||||
/// :nodoc:
|
||||
public init?(rawValue: String) {
|
||||
self.init(rawValue)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import SwiftUI
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension KeyboardShortcuts {
|
||||
/**
|
||||
A SwiftUI `View` that lets the user record a keyboard shortcut.
|
||||
|
||||
You would usually put this in your preferences window.
|
||||
|
||||
It automatically prevents choosing a keyboard shortcut that is already taken by the system or by the app's main menu by showing a user-friendly alert to the user.
|
||||
|
||||
It takes care of storing the keyboard shortcut in `UserDefaults` for you.
|
||||
|
||||
```
|
||||
import SwiftUI
|
||||
import KeyboardShortcuts
|
||||
|
||||
struct PreferencesView: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Toggle Unicorn Mode:")
|
||||
KeyboardShortcuts.Recorder(for: .toggleUnicornMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public struct Recorder: NSViewRepresentable { // swiftlint:disable:this type_name
|
||||
/// :nodoc:
|
||||
public typealias NSViewType = RecorderCocoa
|
||||
|
||||
private let name: Name
|
||||
|
||||
public init(for name: Name) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
public func makeNSView(context: Context) -> NSViewType { .init(for: name) }
|
||||
|
||||
/// :nodoc:
|
||||
public func updateNSView(_ nsView: NSViewType, context: Context) {}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
struct SwiftUI_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
KeyboardShortcuts.Recorder(for: .Name("xcodePreview"))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
import Cocoa
|
||||
import Carbon.HIToolbox
|
||||
|
||||
extension KeyboardShortcuts {
|
||||
/**
|
||||
A `NSView` that lets the user record a keyboard shortcut.
|
||||
|
||||
You would usually put this in your preferences window.
|
||||
|
||||
It automatically prevents choosing a keyboard shortcut that is already taken by the system or by the app's main menu by showing a user-friendly alert to the user.
|
||||
|
||||
It takes care of storing the keyboard shortcut in `UserDefaults` for you.
|
||||
|
||||
```
|
||||
import Cocoa
|
||||
import KeyboardShortcuts
|
||||
|
||||
final class PreferencesViewController: NSViewController {
|
||||
override func loadView() {
|
||||
view = NSView()
|
||||
|
||||
let recorder = KeyboardShortcuts.RecorderCocoa(for: .toggleUnicornMode)
|
||||
view.addSubview(recorder)
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public final class RecorderCocoa: NSSearchField, NSSearchFieldDelegate {
|
||||
private let minimumWidth: Double = 130
|
||||
private var eventMonitor: LocalEventMonitor?
|
||||
private let shortcutName: Name
|
||||
|
||||
/// :nodoc:
|
||||
override public var canBecomeKeyView: Bool { false }
|
||||
|
||||
/// :nodoc:
|
||||
override public var intrinsicContentSize: CGSize {
|
||||
var size = super.intrinsicContentSize
|
||||
size.width = CGFloat(minimumWidth)
|
||||
return size
|
||||
}
|
||||
|
||||
public required init(for name: Name) {
|
||||
self.shortcutName = name
|
||||
|
||||
super.init(frame: .zero)
|
||||
self.delegate = self
|
||||
self.placeholderString = "Click to Record"
|
||||
self.centersPlaceholder = true
|
||||
self.alignment = .center
|
||||
(self.cell as? NSSearchFieldCell)?.searchButtonCell = nil
|
||||
|
||||
if let shortcut = userDefaultsGet(name: shortcutName) {
|
||||
self.stringValue = "\(shortcut)"
|
||||
}
|
||||
|
||||
self.wantsLayer = true
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
||||
self.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
self.widthAnchor.constraint(greaterThanOrEqualToConstant: CGFloat(minimumWidth)).isActive = true
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
public func controlTextDidChange(_ object: Notification) {
|
||||
if stringValue.isEmpty {
|
||||
userDefaultsRemove(name: shortcutName)
|
||||
}
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
public func controlTextDidEndEditing(_ object: Notification) {
|
||||
eventMonitor = nil
|
||||
placeholderString = "Click to Record"
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
override public func becomeFirstResponder() -> Bool {
|
||||
let shouldBecomeFirstResponder = super.becomeFirstResponder()
|
||||
|
||||
guard shouldBecomeFirstResponder else {
|
||||
return shouldBecomeFirstResponder
|
||||
}
|
||||
|
||||
placeholderString = "Press Shortcut"
|
||||
hideCaret()
|
||||
|
||||
eventMonitor = LocalEventMonitor(events: [.keyDown]) { [weak self] event in
|
||||
guard let self = self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if
|
||||
event.modifiers.isEmpty,
|
||||
event.specialKey == .tab
|
||||
{
|
||||
return event
|
||||
}
|
||||
|
||||
if
|
||||
event.modifiers.isEmpty,
|
||||
event.keyCode == kVK_Escape // TODO: Make this strongly typed.
|
||||
{
|
||||
self.blur()
|
||||
return nil
|
||||
}
|
||||
|
||||
if
|
||||
event.modifiers.isEmpty &&
|
||||
(
|
||||
event.specialKey == .delete ||
|
||||
event.specialKey == .deleteForward ||
|
||||
event.specialKey == .backspace
|
||||
)
|
||||
{
|
||||
self.clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
guard
|
||||
(
|
||||
!event.modifiers.isEmpty ||
|
||||
event.specialKey?.isFunctionKey == true
|
||||
),
|
||||
let shortcut = Shortcut(event: event)
|
||||
else {
|
||||
NSSound.beep()
|
||||
return nil
|
||||
}
|
||||
|
||||
if let menuItem = shortcut.takenByMainMenu {
|
||||
NSAlert.showModal(
|
||||
for: self.window,
|
||||
message: "This keyboard shortcut cannot be used as it's already used by the “\(menuItem.title)” menu item."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
guard !shortcut.isTakenBySystem else {
|
||||
NSAlert.showModal(
|
||||
for: self.window,
|
||||
message: "This keyboard shortcut cannot be used as it's already a system-wide keyboard shortcut.",
|
||||
// TODO: Add button to offer to open the relevant system preference pane for the user.
|
||||
informativeText: "Most system-wide keyboard shortcuts can be changed in “System Preferences › Keyboard › Shortcuts“."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
self.stringValue = "\(shortcut)"
|
||||
userDefaultsSet(name: self.shortcutName, shortcut: shortcut)
|
||||
self.blur()
|
||||
|
||||
return nil
|
||||
}.start()
|
||||
|
||||
return shouldBecomeFirstResponder
|
||||
}
|
||||
|
||||
private func blur() {
|
||||
window?.makeFirstResponder(nil)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
import Cocoa
|
||||
import Carbon.HIToolbox
|
||||
|
||||
extension KeyboardShortcuts {
|
||||
/// A keyboard shortcut.
|
||||
public struct Shortcut: Hashable, Codable {
|
||||
/// Carbon modifiers are not always stored as the same number.
|
||||
/// For example, the system has `⌃F2` stored with the modifiers number `135168`, but if you press the keyboard shortcut, you get `4096`.
|
||||
private static func normalizeModifiers(_ carbonModifiers: Int) -> Int {
|
||||
NSEvent.ModifierFlags(carbon: carbonModifiers).carbon
|
||||
}
|
||||
|
||||
public let carbonKeyCode: Int
|
||||
public let carbonModifiers: Int
|
||||
|
||||
public var key: Key? { Key(rawValue: carbonKeyCode) }
|
||||
public var modifiers: NSEvent.ModifierFlags { NSEvent.ModifierFlags(carbon: carbonModifiers) }
|
||||
|
||||
/// Create a shortcut from a key code number and modifier code.
|
||||
public init(carbonKeyCode: Int, carbonModifiers: Int = 0) {
|
||||
self.carbonKeyCode = carbonKeyCode
|
||||
self.carbonModifiers = Self.normalizeModifiers(carbonModifiers)
|
||||
}
|
||||
|
||||
/// Create a keyboard shortcut from a strongly-typed key and modifiers.
|
||||
public init(_ key: Key, modifiers: NSEvent.ModifierFlags = []) {
|
||||
self.init(
|
||||
carbonKeyCode: key.rawValue,
|
||||
carbonModifiers: modifiers.carbon
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a keyboard shortcut from a key event.
|
||||
public init?(event: NSEvent) {
|
||||
guard event.isKeyEvent else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
carbonKeyCode: Int(event.keyCode),
|
||||
carbonModifiers: event.modifierFlags.carbon
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyboardShortcuts.Shortcut {
|
||||
/// System-defined keyboard shortcuts.
|
||||
static var system: [Self] { CarbonKeyboardShortcuts.system }
|
||||
|
||||
/// Check whether the keyboard shortcut is already taken by the system.
|
||||
var isTakenBySystem: Bool { Self.system.contains(self) }
|
||||
}
|
||||
|
||||
extension KeyboardShortcuts.Shortcut {
|
||||
/// Recursively finds a menu item in the given menu that has a matching key equivalent and modifier.
|
||||
func menuItemWithMatchingShortcut(in menu: NSMenu) -> NSMenuItem? {
|
||||
for item in menu.items {
|
||||
if
|
||||
keyToCharacter() == item.keyEquivalent,
|
||||
modifiers == item.keyEquivalentModifierMask
|
||||
{
|
||||
return item
|
||||
}
|
||||
|
||||
if
|
||||
let submenu = item.submenu,
|
||||
let menuItem = menuItemWithMatchingShortcut(in: submenu)
|
||||
{
|
||||
return menuItem
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns a menu item in the app's main menu that has a matching key equivalent and modifier.
|
||||
var takenByMainMenu: NSMenuItem? {
|
||||
guard let mainMenu = NSApp.mainMenu else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return menuItemWithMatchingShortcut(in: mainMenu)
|
||||
}
|
||||
}
|
||||
|
||||
private var keyToCharacterMapping: [KeyboardShortcuts.Key: String] = [
|
||||
.carriageReturn: "↩",
|
||||
.delete: "⌫",
|
||||
.deleteForward: "⌦",
|
||||
.downArrow: "↓",
|
||||
.end: "↘",
|
||||
.escape: "⎋",
|
||||
.help: "?⃝",
|
||||
.home: "↖",
|
||||
.leftArrow: "←",
|
||||
.pageDown: "⇟",
|
||||
.pageUp: "⇞",
|
||||
.rightArrow: "→",
|
||||
.space: "Space",
|
||||
.tab: "⇥",
|
||||
.upArrow: "↑",
|
||||
.f1: "F1",
|
||||
.f2: "F2",
|
||||
.f3: "F3",
|
||||
.f4: "F4",
|
||||
.f5: "F5",
|
||||
.f6: "F6",
|
||||
.f7: "F7",
|
||||
.f8: "F8",
|
||||
.f9: "F9",
|
||||
.f10: "F10",
|
||||
.f11: "F11",
|
||||
.f12: "F12",
|
||||
.f13: "F13",
|
||||
.f14: "F14",
|
||||
.f15: "F15",
|
||||
.f16: "F16",
|
||||
.f17: "F17",
|
||||
.f18: "F18",
|
||||
.f19: "F19",
|
||||
.f20: "F20",
|
||||
.f21: "F21",
|
||||
.f22: "F22",
|
||||
.f23: "F23",
|
||||
.f24: "F24",
|
||||
.f25: "F25",
|
||||
.f26: "F26",
|
||||
.f27: "F27",
|
||||
.f28: "F28",
|
||||
.f29: "F29",
|
||||
.f30: "F30",
|
||||
.f31: "F31",
|
||||
.f32: "F32",
|
||||
.f33: "F33",
|
||||
.f34: "F34",
|
||||
.f35: "F35"
|
||||
]
|
||||
|
||||
extension KeyboardShortcuts.Shortcut: CustomStringConvertible {
|
||||
fileprivate func keyToCharacter() -> String? {
|
||||
// Some characters cannot be automatically translated.
|
||||
if
|
||||
let key = key,
|
||||
let character = keyToCharacterMapping[key]
|
||||
{
|
||||
return character
|
||||
}
|
||||
|
||||
let source = TISCopyCurrentASCIICapableKeyboardLayoutInputSource().takeUnretainedValue()
|
||||
let layoutData = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)
|
||||
let dataRef = unsafeBitCast(layoutData, to: CFData.self)
|
||||
let keyLayout = unsafeBitCast(CFDataGetBytePtr(dataRef), to: UnsafePointer<CoreServices.UCKeyboardLayout>.self)
|
||||
var deadKeyState: UInt32 = 0
|
||||
let maxCharacters = 256
|
||||
var length = 0
|
||||
var characters = [UniChar](repeating: 0, count: maxCharacters)
|
||||
|
||||
let error = CoreServices.UCKeyTranslate(
|
||||
keyLayout,
|
||||
UInt16(carbonKeyCode),
|
||||
UInt16(CoreServices.kUCKeyActionDisplay),
|
||||
UInt32(carbonModifiers),
|
||||
UInt32(LMGetKbdType()),
|
||||
OptionBits(CoreServices.kUCKeyTranslateNoDeadKeysBit),
|
||||
&deadKeyState,
|
||||
maxCharacters,
|
||||
&length,
|
||||
&characters
|
||||
)
|
||||
|
||||
guard error == noErr else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(utf16CodeUnits: characters, count: length)
|
||||
}
|
||||
|
||||
/**
|
||||
The string representation of the keyboard shortcut.
|
||||
|
||||
```
|
||||
print(Shortcut(.a, modifiers: [.command]))
|
||||
//=> "⌘A"
|
||||
```
|
||||
*/
|
||||
public var description: String {
|
||||
modifiers.description + (keyToCharacter()?.uppercased() ?? "<EFBFBD>")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
import Cocoa
|
||||
import Carbon.HIToolbox
|
||||
|
||||
|
||||
extension Data {
|
||||
var string: String? { String(data: self, encoding: .utf8) }
|
||||
}
|
||||
|
||||
|
||||
extension NSEvent {
|
||||
var isKeyEvent: Bool { type == .keyDown || type == .keyUp }
|
||||
}
|
||||
|
||||
|
||||
extension NSTextField {
|
||||
func hideCaret() {
|
||||
(currentEditor() as? NSTextView)?.insertionPointColor = .clear
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Listen to local events.
|
||||
|
||||
- Important: Don't foret to call `.start()`.
|
||||
|
||||
```
|
||||
eventMonitor = LocalEventMonitor(events: [.leftMouseDown, .rightMouseDown]) { event in
|
||||
// Do something
|
||||
|
||||
return event
|
||||
}.start()
|
||||
```
|
||||
*/
|
||||
final class LocalEventMonitor {
|
||||
private let events: NSEvent.EventTypeMask
|
||||
private let callback: (NSEvent) -> NSEvent?
|
||||
private weak var monitor: AnyObject?
|
||||
|
||||
init(events: NSEvent.EventTypeMask, callback: @escaping (NSEvent) -> NSEvent?) {
|
||||
self.events = events
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func start() -> Self {
|
||||
monitor = NSEvent.addLocalMonitorForEvents(matching: events, handler: callback) as AnyObject
|
||||
return self
|
||||
}
|
||||
|
||||
func stop() {
|
||||
guard let monitor = monitor else {
|
||||
return
|
||||
}
|
||||
|
||||
NSEvent.removeMonitor(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSEvent {
|
||||
static var modifiers: ModifierFlags {
|
||||
modifierFlags
|
||||
.intersection(.deviceIndependentFlagsMask)
|
||||
// We remove `capsLock` as it shouldn't affect the modifiers.
|
||||
// We remove `numericPad`/`function` as arrow keys trigger it, use `event.specialKeys` instead.
|
||||
.subtracting([.capsLock, .numericPad, .function])
|
||||
}
|
||||
|
||||
/**
|
||||
Real modifiers.
|
||||
|
||||
- Note: Prefer this over `.modifierFlags`.
|
||||
|
||||
```
|
||||
// Check if Command is one of possible more modifiers keys
|
||||
event.modifiers.contains(.command)
|
||||
|
||||
// Check if Command is the only modifier key
|
||||
event.modifiers == .command
|
||||
|
||||
// Check if Command and Shift are the only modifiers
|
||||
event.modifiers == [.command, .shift]
|
||||
```
|
||||
*/
|
||||
var modifiers: ModifierFlags {
|
||||
modifierFlags
|
||||
.intersection(.deviceIndependentFlagsMask)
|
||||
// We remove `capsLock` as it shouldn't affect the modifiers.
|
||||
// We remove `numericPad`/`function` as arrow keys trigger it, use `event.specialKeys` instead.
|
||||
.subtracting([.capsLock, .numericPad, .function])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSSearchField {
|
||||
/// Clear the search field.
|
||||
func clear() {
|
||||
(cell as? NSSearchFieldCell)?.cancelButtonCell?.performClick(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSAlert {
|
||||
/// Show an alert as a window-modal sheet, or as an app-modal (window-independent) alert if the window is `nil` or not given.
|
||||
@discardableResult
|
||||
static func showModal(
|
||||
for window: NSWindow? = nil,
|
||||
message: String,
|
||||
informativeText: String? = nil,
|
||||
style: Style = .warning,
|
||||
icon: NSImage? = nil
|
||||
) -> NSApplication.ModalResponse {
|
||||
NSAlert(
|
||||
message: message,
|
||||
informativeText: informativeText,
|
||||
style: style,
|
||||
icon: icon
|
||||
).runModal(for: window)
|
||||
}
|
||||
|
||||
convenience init(
|
||||
message: String,
|
||||
informativeText: String? = nil,
|
||||
style: Style = .warning,
|
||||
icon: NSImage? = nil
|
||||
) {
|
||||
self.init()
|
||||
self.messageText = message
|
||||
self.alertStyle = style
|
||||
self.icon = icon
|
||||
|
||||
if let informativeText = informativeText {
|
||||
self.informativeText = informativeText
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the alert as a window-modal sheet, or as an app-modal (window-independent) alert if the window is `nil` or not given.
|
||||
@discardableResult
|
||||
func runModal(for window: NSWindow? = nil) -> NSApplication.ModalResponse {
|
||||
guard let window = window else {
|
||||
return runModal()
|
||||
}
|
||||
|
||||
beginSheetModal(for: window) { returnCode in
|
||||
NSApp.stopModal(withCode: returnCode)
|
||||
}
|
||||
|
||||
return NSApp.runModal(for: window)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSEvent.ModifierFlags {
|
||||
var carbon: Int {
|
||||
var modifierFlags = 0
|
||||
|
||||
if contains(.control) {
|
||||
modifierFlags |= controlKey
|
||||
}
|
||||
|
||||
if contains(.option) {
|
||||
modifierFlags |= optionKey
|
||||
}
|
||||
|
||||
if contains(.shift) {
|
||||
modifierFlags |= shiftKey
|
||||
}
|
||||
|
||||
if contains(.command) {
|
||||
modifierFlags |= cmdKey
|
||||
}
|
||||
|
||||
return modifierFlags
|
||||
}
|
||||
|
||||
init(carbon: Int) {
|
||||
self.init()
|
||||
|
||||
if carbon & controlKey == controlKey {
|
||||
insert(.control)
|
||||
}
|
||||
|
||||
if carbon & optionKey == optionKey {
|
||||
insert(.option)
|
||||
}
|
||||
|
||||
if carbon & shiftKey == shiftKey {
|
||||
insert(.shift)
|
||||
}
|
||||
|
||||
if carbon & cmdKey == cmdKey {
|
||||
insert(.command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
extension NSEvent.ModifierFlags: CustomStringConvertible {
|
||||
/**
|
||||
The string representation of the modifier flags.
|
||||
|
||||
```
|
||||
print(NSEvent.ModifierFlags([.command, .shift]))
|
||||
//=> "⇧⌘"
|
||||
```
|
||||
*/
|
||||
public var description: String {
|
||||
var description = ""
|
||||
|
||||
if contains(.control) {
|
||||
description += "⌃"
|
||||
}
|
||||
|
||||
if contains(.option) {
|
||||
description += "⌥"
|
||||
}
|
||||
|
||||
if contains(.shift) {
|
||||
description += "⇧"
|
||||
}
|
||||
|
||||
if contains(.command) {
|
||||
description += "⌘"
|
||||
}
|
||||
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension NSEvent.SpecialKey {
|
||||
static let functionKeys: Set<Self> = [
|
||||
.f1,
|
||||
.f2,
|
||||
.f3,
|
||||
.f4,
|
||||
.f5,
|
||||
.f6,
|
||||
.f7,
|
||||
.f8,
|
||||
.f9,
|
||||
.f10,
|
||||
.f11,
|
||||
.f12,
|
||||
.f13,
|
||||
.f14,
|
||||
.f15,
|
||||
.f16,
|
||||
.f17,
|
||||
.f18,
|
||||
.f19,
|
||||
.f20,
|
||||
.f21,
|
||||
.f22,
|
||||
.f23,
|
||||
.f24,
|
||||
.f25,
|
||||
.f26,
|
||||
.f27,
|
||||
.f28,
|
||||
.f29,
|
||||
.f30,
|
||||
.f31,
|
||||
.f32,
|
||||
.f33,
|
||||
.f34,
|
||||
.f35
|
||||
]
|
||||
|
||||
var isFunctionKey: Bool { Self.functionKeys.contains(self) }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,156 @@
|
|||
<div align="center">
|
||||
<img width="900" src="logo.png" alt="KeyboardShortcuts">
|
||||
<br>
|
||||
</div>
|
||||
|
||||
This package lets you add support for user-customizable global keyboard shortcuts to your macOS app in minutes. It's fully sandbox and Mac App Store compatible. And it's used in production by [Dato](https://sindresorhus.com/dato), [Jiffy](https://sindresorhus.com/jiffy), and [Lungo](https://sindresorhus.com/lungo).
|
||||
|
||||
This package is still in its early days. I'm happy to accept more configurability and features. PR welcome! What you see here is just what I needed for my own apps.
|
||||
|
||||
<img src="screenshot.png" width="532">
|
||||
|
||||
## Requirements
|
||||
|
||||
macOS 10.11+
|
||||
|
||||
## Install
|
||||
|
||||
#### Swift Package Manager
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/sindresorhus/KeyboardShortcuts", from: "0.0.0")
|
||||
```
|
||||
|
||||
You need to set the build setting “Other Linker Flags” to `-weak_framework Combine` to work around [this Xcode bug](https://github.com/feedback-assistant/reports/issues/44).
|
||||
|
||||
#### Carthage
|
||||
|
||||
```
|
||||
github "sindresorhus/KeyboardShortcuts"
|
||||
```
|
||||
|
||||
#### CocoaPods
|
||||
|
||||
```ruby
|
||||
pod 'KeyboardShortcuts'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, register a name for the keyboard shortcut.
|
||||
|
||||
`Constants.swift`
|
||||
|
||||
```swift
|
||||
import KeyboardShortcuts
|
||||
|
||||
extension KeyboardShortcuts.Name {
|
||||
static let toggleUnicornMode = Name("toggleUnicornMode")
|
||||
}
|
||||
```
|
||||
|
||||
You can then refer to this strongly-typed name in other places.
|
||||
|
||||
You will want to make a view where the user can choose a keyboard shortcut.
|
||||
|
||||
`PreferencesView.swift`
|
||||
|
||||
```swift
|
||||
import SwiftUI
|
||||
import KeyboardShortcuts
|
||||
|
||||
struct PreferencesView: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text("Toggle Unicorn Mode:")
|
||||
KeyboardShortcuts.Recorder(for: .toggleUnicornMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*There's also [support for Cocoa](#cocoa) instead of SwiftUI.*
|
||||
|
||||
`KeyboardShortcuts.Recorder` takes care of storing the keyboard shortcut in `UserDefaults` and also warning the user if the chosen keyboard shortcut is already used by the system or the app's main menu.
|
||||
|
||||
Add a listener for when the user presses their chosen keyboard shortcut.
|
||||
|
||||
`AppDelegate.swift`
|
||||
|
||||
```swift
|
||||
import Cocoa
|
||||
import KeyboardShortcuts
|
||||
|
||||
@NSApplicationMain
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
KeyboardShortcuts.onKeyUp(for: .toggleUnicornMode) {
|
||||
// The user pressed the keyboard shortcut for “unicorn mode”!
|
||||
self.isUnicornMode.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*You can also listen to key down with `.onKeyDown()`*
|
||||
|
||||
**That's all! ✨**
|
||||
|
||||
You can find a complete example by opening `KeyboardShortcuts.xcodeproj` and then running the `KeyboardShortcutsExample` target.
|
||||
|
||||
#### Cocoa
|
||||
|
||||
Use [`KeyboardShortcuts.RecorderCocoa`](Sources/KeyboardShortcuts/RecorderCocoa.swift) instead of `KeyboardShortcuts.Recorder`.
|
||||
|
||||
```swift
|
||||
import Cocoa
|
||||
import KeyboardShortcuts
|
||||
|
||||
final class PreferencesViewController: NSViewController {
|
||||
override func loadView() {
|
||||
view = NSView()
|
||||
|
||||
let recorder = KeyboardShortcuts.RecorderCocoa(for: .toggleUnicornMode)
|
||||
view.addSubview(recorder)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
[See the API docs.](https://sindresorhus.com/KeyboardShortcuts)
|
||||
|
||||
## FAQ
|
||||
|
||||
#### How is it different from [`MASShortcut`](https://github.com/shpakovski/MASShortcut)?
|
||||
|
||||
This package:
|
||||
- Written in Swift with a swifty API.
|
||||
- More native-looking UI component.
|
||||
- SwiftUI component included.
|
||||
- Support for listening to key down, not just key up.
|
||||
- Swift Package Manager support.
|
||||
|
||||
`MASShortcut`:
|
||||
- More mature.
|
||||
- More features.
|
||||
- Localized.
|
||||
|
||||
#### How is it different from [`HotKey`](https://github.com/soffes/HotKey)?
|
||||
|
||||
`HotKey` is good for adding hard-coded keyboard shortcuts, but it doesn't provide any UI component for the user to choose their own keyboard shortcuts.
|
||||
|
||||
#### Why is this package importing `Carbon`? Isn't that deprecated?
|
||||
|
||||
Most of the Carbon APIs were deprecated years ago, but there are some left that Apple never shipped modern replacements for. This includes registering global keyboard shortcuts. However, you should not need to worry about this. Apple will for sure ship new APIs before deprecating the Carbon APIs used here.
|
||||
|
||||
#### Does this package cause any permission dialogs?
|
||||
|
||||
No.
|
||||
|
||||
## Related
|
||||
|
||||
- [Preferences](https://github.com/sindresorhus/Preferences) - Add a preferences window to your macOS app in minutes
|
||||
- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults
|
||||
- [LaunchAtLogin](https://github.com/sindresorhus/LaunchAtLogin) - Add "Launch at Login" functionality to your macOS app
|
||||
- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift)
|
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Loading…
Reference in New Issue