Skip to content

Commit

Permalink
Improve support for strict concurrency
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Feb 20, 2024
1 parent ac12762 commit 3b11952
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 81 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 Sources/KeyboardShortcuts/Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension KeyboardShortcuts {
/**
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
2 changes: 1 addition & 1 deletion Sources/KeyboardShortcuts/KeyboardShortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ public enum KeyboardShortcuts {

extension KeyboardShortcuts {
@available(macOS 10.15, *)
public enum EventType {
public enum EventType: Sendable {
case keyDown
case keyUp
}
Expand Down
39 changes: 17 additions & 22 deletions Sources/KeyboardShortcuts/NSMenuItem++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import AppKit

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

@MainActor
private func clearShortcut() {
keyEquivalent = ""
keyEquivalentModifierMask = []
Expand Down Expand Up @@ -44,10 +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)
NotificationCenter.default.removeObserver(AssociatedKeys.observer[self] as Any)
AssociatedKeys.observer[self] = nil
return
}
Expand All @@ -59,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 @@ -68,7 +71,9 @@ extension NSMenuItem {
return
}

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

Expand All @@ -84,28 +89,18 @@ 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
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/KeyboardShortcuts/Name.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,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
14 changes: 8 additions & 6 deletions Sources/KeyboardShortcuts/Shortcut.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension KeyboardShortcuts {
/**
A keyboard shortcut.
*/
public struct Shortcut: Hashable, Codable {
public struct Shortcut: Hashable, Codable, Sendable {
/**
Carbon modifiers are not always stored as the same number.

Expand Down Expand Up @@ -111,6 +111,7 @@ extension KeyboardShortcuts.Shortcut {
/**
Recursively finds a menu item in the given menu that has a matching key equivalent and modifier.
*/
@MainActor
func menuItemWithMatchingShortcut(in menu: NSMenu) -> NSMenuItem? {
for item in menu.items {
var keyEquivalent = item.keyEquivalent
Expand Down Expand Up @@ -142,6 +143,7 @@ extension KeyboardShortcuts.Shortcut {
/**
Returns a menu item in the app's main menu that has a matching key equivalent and modifier.
*/
@MainActor
var takenByMainMenu: NSMenuItem? {
guard let mainMenu = NSApp.mainMenu else {
return nil
Expand All @@ -151,7 +153,7 @@ extension KeyboardShortcuts.Shortcut {
}
}

private var keyToCharacterMapping: [KeyboardShortcuts.Key: String] = [
private let keyToCharacterMapping: [KeyboardShortcuts.Key: String] = [
.return: "",
.delete: "",
.deleteForward: "",
Expand Down Expand Up @@ -216,7 +218,7 @@ private func stringFromKeyCode(_ keyCode: Int) -> String {
String(format: "%C", keyCode)
}

private var keyToKeyEquivalentString: [KeyboardShortcuts.Key: String] = [
private let keyToKeyEquivalentString: [KeyboardShortcuts.Key: String] = [
.space: stringFromKeyCode(0x20),
.f1: stringFromKeyCode(NSF1FunctionKey),
.f2: stringFromKeyCode(NSF2FunctionKey),
Expand All @@ -241,10 +243,8 @@ private var keyToKeyEquivalentString: [KeyboardShortcuts.Key: String] = [
]

extension KeyboardShortcuts.Shortcut {
@MainActor // `TISGetInputSourceProperty` crashes if called on a non-main thread.
fileprivate func keyToCharacter() -> String? {
// `TISCopyCurrentASCIICapableKeyboardLayoutInputSource` works on a background thread, but crashes when used in a `NSBackgroundActivityScheduler` task, so we guard against that. It only crashes when running from Xcode, not in release builds, but it's probably safest to not call it from a `NSBackgroundActivityScheduler` no matter what.
assert(!DispatchQueue.isCurrentQueueNSBackgroundActivitySchedulerQueue, "This method cannot be used in a `NSBackgroundActivityScheduler` task")

// Some characters cannot be automatically translated.
if
let key,
Expand Down Expand Up @@ -293,6 +293,7 @@ extension KeyboardShortcuts.Shortcut {

- Note: Don't forget to also pass `.modifiers` to `NSMenuItem#keyEquivalentModifierMask`.
*/
@MainActor
var keyEquivalent: String {
let keyString = keyToCharacter() ?? ""

Expand Down Expand Up @@ -320,6 +321,7 @@ extension KeyboardShortcuts.Shortcut: CustomStringConvertible {
//=> "⌘A"
```
*/
@MainActor
public var description: String {
// We use `.capitalized` so it correctly handles “⌘Space”.
modifiers.description + (keyToCharacter()?.capitalized ?? "")
Expand Down
20 changes: 0 additions & 20 deletions Sources/KeyboardShortcuts/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,26 +429,6 @@ final class ObjectAssociation<T> {
}


extension DispatchQueue {
/**
Label of the current dispatch queue.

- Important: Only meant for debugging purposes.

```
DispatchQueue.currentQueueLabel
//=> "com.apple.main-thread"
```
*/
static var currentQueueLabel: String { String(cString: __dispatch_queue_get_label(nil)) }

/**
Whether the current queue is a `NSBackgroundActivityScheduler` task.
*/
static var isCurrentQueueNSBackgroundActivitySchedulerQueue: Bool { currentQueueLabel.hasPrefix("com.apple.xpc.activity.") }
}


@available(macOS 10.15, *)
extension HorizontalAlignment {
private enum ControlAlignment: AlignmentID {
Expand Down

0 comments on commit 3b11952

Please sign in to comment.