Skip to content

Commit

Permalink
Merge branch 'sindresorhus:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
godbout authored Apr 13, 2024
2 parents 160de10 + 09e4a10 commit a6cb666
Show file tree
Hide file tree
Showing 16 changed files with 102 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1530"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
14 changes: 9 additions & 5 deletions Example/KeyboardShortcutsExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
E36FB94B2609BA43004272D9 /* MainScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreen.swift; sourceTree = "<group>"; };
E36FB94D2609BA45004272D9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
E36FB9502609BA45004272D9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
E36FB9522609BA45004272D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E36FB9532609BA45004272D9 /* KeyboardShortcutsExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KeyboardShortcutsExample.entitlements; sourceTree = "<group>"; };
E36FB9622609BB83004272D9 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E36FB9652609BF3D004272D9 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -75,7 +74,6 @@
E36FB94B2609BA43004272D9 /* MainScreen.swift */,
E36FB9652609BF3D004272D9 /* Utilities.swift */,
E36FB94D2609BA45004272D9 /* Assets.xcassets */,
E36FB9522609BA45004272D9 /* Info.plist */,
E36FB9532609BA45004272D9 /* KeyboardShortcutsExample.entitlements */,
E36FB94F2609BA45004272D9 /* Preview Content */,
);
Expand Down Expand Up @@ -128,7 +126,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1240;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1530;
TargetAttributes = {
E36FB9452609BA43004272D9 = {
CreatedOnToolsVersion = 12.4;
Expand Down Expand Up @@ -184,6 +182,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
Expand Down Expand Up @@ -218,6 +217,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -246,6 +246,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
Expand Down Expand Up @@ -280,6 +281,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down Expand Up @@ -312,14 +314,15 @@
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = KeyboardShortcutsExample/Info.plist;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Debug;
Expand All @@ -339,14 +342,15 @@
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = KeyboardShortcutsExample/Info.plist;
GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.sindresorhus.KeyboardShortcutsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 5.0;
};
name = Release;
Expand Down
24 changes: 0 additions & 24 deletions Example/KeyboardShortcutsExample/Info.plist

This file was deleted.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ let package = Package(
name: "KeyboardShortcuts",
defaultLocalization: "en",
platforms: [
.macOS(.v10_13)
.macOS(.v10_15)
],
products: [
.library(
Expand Down
2 changes: 2 additions & 0 deletions Sources/KeyboardShortcuts/CarbonKeyboardShortcuts.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if os(macOS)
import Carbon.HIToolbox

private func carbonKeyboardShortcutsEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {
Expand Down Expand Up @@ -345,3 +346,4 @@ extension CarbonKeyboardShortcuts {
}
}
}
#endif
4 changes: 3 additions & 1 deletion Sources/KeyboardShortcuts/Key.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#if os(macOS)
import Carbon.HIToolbox

extension KeyboardShortcuts {
// swiftlint:disable identifier_name
/**
Represents a key on the keyboard.
*/
public struct Key: Hashable, RawRepresentable {
public struct Key: Hashable, RawRepresentable, Sendable {
// MARK: Letters

public static let a = Self(kVK_ANSI_A)
Expand Down Expand Up @@ -192,3 +193,4 @@ extension KeyboardShortcuts.Key {
*/
var isFunctionKey: Bool { Self.functionKeys.contains(self) }
}
#endif
39 changes: 3 additions & 36 deletions Sources/KeyboardShortcuts/KeyboardShortcuts.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if os(macOS)
import AppKit.NSMenu

/**
Expand Down Expand Up @@ -456,8 +457,7 @@ public enum KeyboardShortcuts {
}

extension KeyboardShortcuts {
@available(macOS 10.15, *)
public enum EventType {
public enum EventType: Sendable {
case keyDown
case keyUp
}
Expand Down Expand Up @@ -491,7 +491,6 @@ extension KeyboardShortcuts {

- Note: This method is not affected by `.removeAllHandlers()`.
*/
@available(macOS 10.15, *)
public static func events(for name: Name) -> AsyncStream<KeyboardShortcuts.EventType> {
AsyncStream { continuation in
let id = UUID()
Expand Down Expand Up @@ -548,44 +547,12 @@ extension KeyboardShortcuts {

- Note: This method is not affected by `.removeAllHandlers()`.
*/
@available(macOS 10.15, *)
public static func events(_ type: EventType, for name: Name) -> AsyncFilterSequence<AsyncStream<EventType>> {
events(for: name).filter { $0 == type }
}

@available(macOS 10.15, *)
@available(*, deprecated, renamed: "events(_:for:)")
public static func on(_ type: EventType, for name: Name) -> AsyncStream<Void> {
AsyncStream { continuation in
let id = UUID()

switch type {
case .keyDown:
streamKeyDownHandlers[name, default: [:]][id] = {
continuation.yield()
}
case .keyUp:
streamKeyUpHandlers[name, default: [:]][id] = {
continuation.yield()
}
}

registerShortcutIfNeeded(for: name)

continuation.onTermination = { _ in
switch type {
case .keyDown:
streamKeyDownHandlers[name]?[id] = nil
case .keyUp:
streamKeyUpHandlers[name]?[id] = nil
}

unregisterShortcutIfNeeded(for: name)
}
}
}
}

extension Notification.Name {
static let shortcutByNameDidChange = Self("KeyboardShortcuts_shortcutByNameDidChange")
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"record_shortcut" = "Nahrajte klávesovú skratku";
"press_shortcut" = "Stlačte klávesovú skratku";
"keyboard_shortcut_used_by_menu_item" = "Túto klávesovú skratku nemožno použiť, pretože ju už používa položka menu “%@”.";
"keyboard_shortcut_used_by_system" = "Túto klávesovú skratku nemožno použiť, pretože ide o klávesovú skratku použivanú pre celý systém.";
"keyboard_shortcuts_can_be_changed" = "Väčšinu klávesových skratiek pre celý systém je možné zmeniť v časti “Systémové nastavenia › Klávesnica › Klávesové skratky”.";
"force_use_shortcut" = "Napriek tomu použiť";
"ok" = "OK";
40 changes: 19 additions & 21 deletions Sources/KeyboardShortcuts/NSMenuItem++.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#if os(macOS)
import AppKit

extension NSMenuItem {
private enum AssociatedKeys {
@MainActor
static let observer = ObjectAssociation<NSObjectProtocol>()
}

@MainActor
private func clearShortcut() {
keyEquivalent = ""
keyEquivalentModifierMask = []
Expand Down Expand Up @@ -43,9 +46,11 @@ extension NSMenuItem {

- Important: You will have to disable the global keyboard shortcut while the menu is open, as otherwise, the keyboard events will be buffered up and triggered when the menu closes. This is because `NSMenu` puts the thread in tracking-mode, which prevents the keyboard events from being received. You can listen to whether a menu is open by implementing `NSMenuDelegate#menuWillOpen` and `NSMenuDelegate#menuDidClose`. You then use `KeyboardShortcuts.disable` and `KeyboardShortcuts.enable`.
*/
@MainActor
public func setShortcut(for name: KeyboardShortcuts.Name?) {
guard let name else {
clearShortcut()
NotificationCenter.default.removeObserver(AssociatedKeys.observer[self] as Any)
AssociatedKeys.observer[self] = nil
return
}
Expand All @@ -57,7 +62,7 @@ extension NSMenuItem {

set()

// TODO: Use AsyncStream when targeting macOS 10.15.
// TODO: Use AsyncStream when targeting macOS 15.
AssociatedKeys.observer[self] = NotificationCenter.default.addObserver(forName: .shortcutByNameDidChange, object: nil, queue: nil) { notification in
guard
let nameInNotification = notification.userInfo?["name"] as? KeyboardShortcuts.Name,
Expand All @@ -66,7 +71,9 @@ extension NSMenuItem {
return
}

set()
DispatchQueue.main.async { // TODO: Use `Task { @MainActor`
set()
}
}
}

Expand All @@ -82,28 +89,19 @@ extension NSMenuItem {
- Important: You will have to disable the global keyboard shortcut while the menu is open, as otherwise, the keyboard events will be buffered up and triggered when the menu closes. This is because `NSMenu` puts the thread in tracking-mode, which prevents the keyboard events from being received. You can listen to whether a menu is open by implementing `NSMenuDelegate#menuWillOpen` and `NSMenuDelegate#menuDidClose`. You then use `KeyboardShortcuts.disable` and `KeyboardShortcuts.enable`.
*/
@_disfavoredOverload
@MainActor
public func setShortcut(_ shortcut: KeyboardShortcuts.Shortcut?) {
func set() {
guard let shortcut else {
clearShortcut()
return
}

keyEquivalent = shortcut.keyEquivalent
keyEquivalentModifierMask = shortcut.modifiers

if #available(macOS 12, *) {
allowsAutomaticKeyEquivalentLocalization = false
}
guard let shortcut else {
clearShortcut()
return
}

// `TISCopyCurrentASCIICapableKeyboardLayoutInputSource` works on a background thread, but crashes when used in a `NSBackgroundActivityScheduler` task, so we ensure it's not run in that queue.
if DispatchQueue.isCurrentQueueNSBackgroundActivitySchedulerQueue {
DispatchQueue.main.async {
set()
}
} else {
set()
keyEquivalent = shortcut.keyEquivalent
keyEquivalentModifierMask = shortcut.modifiers

if #available(macOS 12, *) {
allowsAutomaticKeyEquivalentLocalization = false
}
}
}
#endif
4 changes: 3 additions & 1 deletion Sources/KeyboardShortcuts/Name.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if os(macOS)
extension KeyboardShortcuts {
/**
The strongly-typed name of the keyboard shortcut.
Expand All @@ -12,7 +13,7 @@ extension KeyboardShortcuts {
}
```
*/
public struct Name: Hashable {
public struct Name: Hashable, Sendable {
// This makes it possible to use `Shortcut` without the namespace.
/// :nodoc:
public typealias Shortcut = KeyboardShortcuts.Shortcut
Expand Down Expand Up @@ -56,3 +57,4 @@ extension KeyboardShortcuts.Name: RawRepresentable {
self.init(rawValue)
}
}
#endif
Loading

0 comments on commit a6cb666

Please sign in to comment.