Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

452 lines
14 KiB
Swift

import AppKit
import Carbon
import Foundation
public enum ShortcutScope {
case global
case whilePluginActive
}
public struct ShortcutModifiers: OptionSet, Hashable, Codable, Sendable {
public let rawValue: UInt8
public static let command = ShortcutModifiers(rawValue: 1 << 0)
public static let control = ShortcutModifiers(rawValue: 1 << 1)
public static let option = ShortcutModifiers(rawValue: 1 << 2)
public static let shift = ShortcutModifiers(rawValue: 1 << 3)
public init(rawValue: UInt8) {
self.rawValue = rawValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.init(rawValue: try container.decode(UInt8.self))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
public var carbonFlags: UInt32 {
var flags: UInt32 = 0
if contains(.command) {
flags |= UInt32(cmdKey)
}
if contains(.control) {
flags |= UInt32(controlKey)
}
if contains(.option) {
flags |= UInt32(optionKey)
}
if contains(.shift) {
flags |= UInt32(shiftKey)
}
return flags
}
public var symbolString: String {
var output = ""
if contains(.control) {
output += "⌃"
}
if contains(.option) {
output += "⌥"
}
if contains(.shift) {
output += "⇧"
}
if contains(.command) {
output += "⌘"
}
return output
}
}
public struct ShortcutBinding: Hashable, Codable, Sendable {
public let keyCode: UInt16
public let modifiers: ShortcutModifiers
public init(keyCode: UInt16, modifiers: ShortcutModifiers) {
self.keyCode = keyCode
self.modifiers = modifiers
}
public var isValid: Bool {
!modifiers.isEmpty && !ShortcutKeyCode.isModifier(keyCode)
}
}
public enum ShortcutCustomization: Equatable, Codable {
case inheritDefault
case custom(ShortcutBinding)
case cleared
private enum CodingKeys: String, CodingKey {
case kind
case binding
}
private enum Kind: String, Codable {
case inheritDefault
case custom
case cleared
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(Kind.self, forKey: .kind)
switch kind {
case .inheritDefault:
self = .inheritDefault
case .custom:
self = .custom(try container.decode(ShortcutBinding.self, forKey: .binding))
case .cleared:
self = .cleared
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .inheritDefault:
try container.encode(Kind.inheritDefault, forKey: .kind)
case let .custom(binding):
try container.encode(Kind.custom, forKey: .kind)
try container.encode(binding, forKey: .binding)
case .cleared:
try container.encode(Kind.cleared, forKey: .kind)
}
}
}
public struct PluginShortcutDefinition: Identifiable {
public let id: String
public let title: String
public let description: String
public let actionID: String
public let scope: ShortcutScope
public let defaultBinding: ShortcutBinding?
public let isRequired: Bool
public let sharedBindingGroupID: String?
public let settingsGroupID: String?
public let settingsGroupTitle: String?
public let settingsGroupDescription: String?
public let settingsControlTitle: String?
public let settingsControlSystemImage: String?
public init(
id: String,
title: String,
description: String,
actionID: String,
scope: ShortcutScope,
defaultBinding: ShortcutBinding?,
isRequired: Bool,
sharedBindingGroupID: String? = nil,
settingsGroupID: String? = nil,
settingsGroupTitle: String? = nil,
settingsGroupDescription: String? = nil,
settingsControlTitle: String? = nil,
settingsControlSystemImage: String? = nil
) {
self.id = id
self.title = title
self.description = description
self.actionID = actionID
self.scope = scope
self.defaultBinding = defaultBinding
self.isRequired = isRequired
self.sharedBindingGroupID = sharedBindingGroupID
self.settingsGroupID = settingsGroupID
self.settingsGroupTitle = settingsGroupTitle
self.settingsGroupDescription = settingsGroupDescription
self.settingsControlTitle = settingsControlTitle
self.settingsControlSystemImage = settingsControlSystemImage
}
}
public struct ShortcutSettingsItem: Identifiable {
public let id: String
public let pluginID: String
public let pluginTitle: String
public let title: String
public let description: String
public let bindingText: String
public let isRequired: Bool
public let canClear: Bool
public let usesDefaultValue: Bool
public let errorMessage: String?
public let settingsGroupID: String?
public let settingsGroupTitle: String?
public let settingsGroupDescription: String?
public let settingsControlTitle: String?
public let settingsControlSystemImage: String?
public init(
id: String,
pluginID: String,
pluginTitle: String,
title: String,
description: String,
bindingText: String,
isRequired: Bool,
canClear: Bool,
usesDefaultValue: Bool,
errorMessage: String?,
settingsGroupID: String? = nil,
settingsGroupTitle: String? = nil,
settingsGroupDescription: String? = nil,
settingsControlTitle: String? = nil,
settingsControlSystemImage: String? = nil
) {
self.id = id
self.pluginID = pluginID
self.pluginTitle = pluginTitle
self.title = title
self.description = description
self.bindingText = bindingText
self.isRequired = isRequired
self.canClear = canClear
self.usesDefaultValue = usesDefaultValue
self.errorMessage = errorMessage
self.settingsGroupID = settingsGroupID
self.settingsGroupTitle = settingsGroupTitle
self.settingsGroupDescription = settingsGroupDescription
self.settingsControlTitle = settingsControlTitle
self.settingsControlSystemImage = settingsControlSystemImage
}
}
public enum ShortcutValidationError: LocalizedError {
case missingModifier
case modifierOnly
case requiredShortcut
case duplicate(ownerDescription: String)
public var errorDescription: String? {
switch self {
case .missingModifier:
return PluginKitLocalization.shortcutValidationMissingModifier
case .modifierOnly:
return PluginKitLocalization.shortcutValidationModifierOnly
case .requiredShortcut:
return PluginKitLocalization.shortcutValidationRequiredShortcut
case let .duplicate(ownerDescription):
return PluginKitLocalization.shortcutValidationDuplicate(ownerDescription: ownerDescription)
}
}
}
public enum ShortcutFormatter {
public static func displayString(for binding: ShortcutBinding?) -> String {
guard let binding else {
return "None"
}
return displayTokens(for: binding).joined(separator: " + ")
}
public static func displayTokens(for binding: ShortcutBinding) -> [String] {
var tokens: [String] = []
if binding.modifiers.contains(.control) {
tokens.append("⌃")
}
if binding.modifiers.contains(.option) {
tokens.append("⌥")
}
if binding.modifiers.contains(.shift) {
tokens.append("⇧")
}
if binding.modifiers.contains(.command) {
tokens.append("⌘")
}
tokens.append(keyDisplayName(for: binding.keyCode))
return tokens
}
public static func keyDisplayName(for keyCode: UInt16) -> String {
switch keyCode {
case UInt16(kVK_ANSI_A): return "A"
case UInt16(kVK_ANSI_B): return "B"
case UInt16(kVK_ANSI_C): return "C"
case UInt16(kVK_ANSI_D): return "D"
case UInt16(kVK_ANSI_E): return "E"
case UInt16(kVK_ANSI_F): return "F"
case UInt16(kVK_ANSI_G): return "G"
case UInt16(kVK_ANSI_H): return "H"
case UInt16(kVK_ANSI_I): return "I"
case UInt16(kVK_ANSI_J): return "J"
case UInt16(kVK_ANSI_K): return "K"
case UInt16(kVK_ANSI_L): return "L"
case UInt16(kVK_ANSI_M): return "M"
case UInt16(kVK_ANSI_N): return "N"
case UInt16(kVK_ANSI_O): return "O"
case UInt16(kVK_ANSI_P): return "P"
case UInt16(kVK_ANSI_Q): return "Q"
case UInt16(kVK_ANSI_R): return "R"
case UInt16(kVK_ANSI_S): return "S"
case UInt16(kVK_ANSI_T): return "T"
case UInt16(kVK_ANSI_U): return "U"
case UInt16(kVK_ANSI_V): return "V"
case UInt16(kVK_ANSI_W): return "W"
case UInt16(kVK_ANSI_X): return "X"
case UInt16(kVK_ANSI_Y): return "Y"
case UInt16(kVK_ANSI_Z): return "Z"
case UInt16(kVK_ANSI_0): return "0"
case UInt16(kVK_ANSI_1): return "1"
case UInt16(kVK_ANSI_2): return "2"
case UInt16(kVK_ANSI_3): return "3"
case UInt16(kVK_ANSI_4): return "4"
case UInt16(kVK_ANSI_5): return "5"
case UInt16(kVK_ANSI_6): return "6"
case UInt16(kVK_ANSI_7): return "7"
case UInt16(kVK_ANSI_8): return "8"
case UInt16(kVK_ANSI_9): return "9"
case UInt16(kVK_ANSI_Minus): return "-"
case UInt16(kVK_ANSI_Equal): return "="
case UInt16(kVK_ANSI_LeftBracket): return "["
case UInt16(kVK_ANSI_RightBracket): return "]"
case UInt16(kVK_ANSI_Backslash): return "\\"
case UInt16(kVK_ANSI_Semicolon): return ";"
case UInt16(kVK_ANSI_Quote): return "'"
case UInt16(kVK_ANSI_Comma): return ","
case UInt16(kVK_ANSI_Period): return "."
case UInt16(kVK_ANSI_Slash): return "/"
case UInt16(kVK_ANSI_Grave): return "`"
case UInt16(kVK_Return): return "↩"
case UInt16(kVK_Tab): return "⇥"
case UInt16(kVK_Space): return "␣"
case UInt16(kVK_Delete): return "⌫"
case UInt16(kVK_ForwardDelete): return "⌦"
case UInt16(kVK_Escape): return "ESC"
case UInt16(kVK_LeftArrow): return "←"
case UInt16(kVK_RightArrow): return "→"
case UInt16(kVK_UpArrow): return "↑"
case UInt16(kVK_DownArrow): return "↓"
case UInt16(kVK_Home): return "↖"
case UInt16(kVK_End): return "↘"
case UInt16(kVK_PageUp): return "⇞"
case UInt16(kVK_PageDown): return "⇟"
case UInt16(kVK_Help): return "Help"
case UInt16(kVK_F1): return "F1"
case UInt16(kVK_F2): return "F2"
case UInt16(kVK_F3): return "F3"
case UInt16(kVK_F4): return "F4"
case UInt16(kVK_F5): return "F5"
case UInt16(kVK_F6): return "F6"
case UInt16(kVK_F7): return "F7"
case UInt16(kVK_F8): return "F8"
case UInt16(kVK_F9): return "F9"
case UInt16(kVK_F10): return "F10"
case UInt16(kVK_F11): return "F11"
case UInt16(kVK_F12): return "F12"
case UInt16(kVK_F13): return "F13"
case UInt16(kVK_F14): return "F14"
case UInt16(kVK_F15): return "F15"
case UInt16(kVK_F16): return "F16"
case UInt16(kVK_F17): return "F17"
case UInt16(kVK_F18): return "F18"
case UInt16(kVK_F19): return "F19"
case UInt16(kVK_F20): return "F20"
default:
return "Key \(keyCode)"
}
}
}
public enum ShortcutKeyCode {
public static let escape = UInt16(kVK_Escape)
private static let modifierKeyCodes: Set<UInt16> = [
UInt16(kVK_Command),
UInt16(kVK_RightCommand),
UInt16(kVK_Shift),
UInt16(kVK_RightShift),
UInt16(kVK_Option),
UInt16(kVK_RightOption),
UInt16(kVK_Control),
UInt16(kVK_RightControl),
UInt16(kVK_CapsLock),
63
]
public static func isModifier(_ keyCode: UInt16) -> Bool {
modifierKeyCodes.contains(keyCode)
}
}
public extension ShortcutModifiers {
static func from(_ flags: NSEvent.ModifierFlags) -> ShortcutModifiers {
var modifiers: ShortcutModifiers = []
let normalizedFlags = flags.intersection(.deviceIndependentFlagsMask)
if normalizedFlags.contains(.command) {
modifiers.insert(.command)
}
if normalizedFlags.contains(.control) {
modifiers.insert(.control)
}
if normalizedFlags.contains(.option) {
modifiers.insert(.option)
}
if normalizedFlags.contains(.shift) {
modifiers.insert(.shift)
}
return modifiers
}
static func from(_ flags: CGEventFlags) -> ShortcutModifiers {
var modifiers: ShortcutModifiers = []
if flags.contains(.maskCommand) {
modifiers.insert(.command)
}
if flags.contains(.maskControl) {
modifiers.insert(.control)
}
if flags.contains(.maskAlternate) {
modifiers.insert(.option)
}
if flags.contains(.maskShift) {
modifiers.insert(.shift)
}
return modifiers
}
}
public extension NSEvent {
var shortcutBindingCandidate: ShortcutBinding? {
ShortcutBinding(
keyCode: keyCode,
modifiers: ShortcutModifiers.from(modifierFlags)
)
}
}