IMKHelper // Fix a TISInputSource installation crash in macOS 10.9.

This commit is contained in:
ShikiSuen 2024-02-10 13:44:43 +08:00
parent cfad082b14
commit df5075972a
6 changed files with 76 additions and 40 deletions

View File

@ -50,7 +50,7 @@ var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
else {
return []
}
return tsInputModeListKey.keys.compactMap { TISInputSource.generate(from: $0) }
return TISInputSource.match(modeIDs: tsInputModeListKey.keys.map(\.description))
}
// MARK: - NSApp Activation Helper

View File

@ -27,7 +27,7 @@ public enum IMKHelper {
"com.apple.keylayout.Dvorak-Right",
]
if #unavailable(macOS 10.13) {
result.append("com.apple.keylayout.US")
result.insert("com.apple.keylayout.US", at: result.startIndex)
result.append("com.apple.keylayout.German")
result.append("com.apple.keylayout.French")
}
@ -49,31 +49,29 @@ public enum IMKHelper {
"org.unknown.keylayout.vChewingMiTAC",
]
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
public static var allowedAlphanumericalTISInputSources: [TISInputSource.KeyboardLayout] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
return arrWhitelistedKeyLayoutsASCII.compactMap { allTISKeyboardLayouts[$0] }
}
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
//
var containerA: [TISInputSource?] = []
var containerB: [TISInputSource?] = []
var containerC: [TISInputSource?] = []
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource.KeyboardLayout?] {
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
//
var containerA: [TISInputSource.KeyboardLayout?] = []
var containerB: [TISInputSource.KeyboardLayout?] = []
var containerC: [TISInputSource.KeyboardLayout] = []
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
Self.arrWhitelistedKeyLayoutsASCII.forEach {
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
containerC.append(neta)
}
let filterSet = Array(Set(arrWhitelistedKeyLayoutsASCII).subtracting(Set(arrDynamicBasicKeyLayouts)))
let matchedGroupBasic = (arrWhitelistedKeyLayoutsASCII + arrDynamicBasicKeyLayouts).compactMap {
allTISKeyboardLayouts[$0]
}
Self.arrDynamicBasicKeyLayouts.forEach {
if let neta = rawDictionary[$0] {
if neta.identifier.contains("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
matchedGroupBasic.forEach { neta in
if filterSet.contains(neta.id) {
containerC.append(neta)
} else if neta.id.hasPrefix("com.apple") {
containerA.append(neta)
} else {
containerB.append(neta)
}
}

View File

@ -12,8 +12,13 @@ import InputMethodKit
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
public extension TISInputSource {
struct KeyboardLayout: Identifiable {
public var id: String
public var titleLocalized: String
}
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
TISInputSource.match(modeIDs: TISInputSource.modes)
}
static var modes: [String] {
@ -22,7 +27,7 @@ public extension TISInputSource {
else {
return []
}
return tsInputModeListKey.keys.map { $0 }
return tsInputModeListKey.keys.map(\.description)
}
@discardableResult static func registerInputMethod() -> Bool {
@ -80,10 +85,6 @@ public extension TISInputSource {
== kCFBooleanTrue
}
static func generate(from identifier: String) -> TISInputSource? {
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
}
var inputModeID: String {
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as String? ?? ""
}
@ -120,9 +121,34 @@ public extension TISInputSource {
return unsafeBitCast(r, to: NSString.self).integerValue as Int? ?? 0
}
static func rawTISInputSources(onlyASCII: Bool = false) -> [String: TISInputSource] {
// Refactored by Shiki Suen.
static func match(identifiers: [String] = [], modeIDs: [String] = [], onlyASCII: Bool = false) -> [TISInputSource] {
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
]
let cfDict = !onlyASCII ? nil : dicConditions as CFDictionary
var resultStack: [TISInputSource] = []
let unionedIDs = NSOrderedSet(array: modeIDs + identifiers).compactMap { $0 as? String }
let retrieved = (TISCreateInputSourceList(cfDict, true)?.takeRetainedValue() as? [TISInputSource]) ?? []
retrieved.forEach { tis in
unionedIDs.forEach { id in
guard tis.identifier == id || tis.inputModeID == id else { return }
if onlyASCII {
guard tis.scriptCode == 0 else { return }
}
resultStack.append(tis)
}
}
// retrieved
return unionedIDs.compactMap { currentIdentifier in
retrieved.first { $0.identifier == currentIdentifier || $0.inputModeID == currentIdentifier }
}
}
/// Mzp .match()
static func rawTISInputSources(onlyASCII: Bool = false) -> [TISInputSource] {
// CFDictionary
//
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
@ -132,10 +158,21 @@ public extension TISInputSource {
if onlyASCII {
result = result.filter { $0.scriptCode == 0 }
}
var resultDictionary: [String: TISInputSource] = [:]
return result
}
/// Derived from rawTISInputSources().
static func getAllTISInputKeyboardLayoutMap() -> [String: TISInputSource.KeyboardLayout] {
// CFDictionary
let dicConditions: [CFString: Any] = [kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString]
//
let result = TISCreateInputSourceList(dicConditions as CFDictionary, true)?.takeRetainedValue() as? [TISInputSource] ?? .init()
var resultDictionary: [String: TISInputSource.KeyboardLayout] = [:]
result.forEach {
resultDictionary[$0.inputModeID] = $0
resultDictionary[$0.identifier] = $0
let newNeta1 = TISInputSource.KeyboardLayout(id: $0.inputModeID, titleLocalized: $0.vChewingLocalizedName)
let newNeta2 = TISInputSource.KeyboardLayout(id: $0.identifier, titleLocalized: $0.vChewingLocalizedName)
resultDictionary[$0.inputModeID] = newNeta1
resultDictionary[$0.identifier] = newNeta2
}
return resultDictionary
}

View File

@ -51,10 +51,11 @@ public extension PrefMgr {
keyboardParser = 0
}
//
if TISInputSource.generate(from: basicKeyboardLayout) == nil {
let matchedResults = TISInputSource.match(identifiers: [basicKeyboardLayout, alphanumericalKeyboardLayout])
if !matchedResults.contains(where: { $0.identifier == basicKeyboardLayout }) {
basicKeyboardLayout = Self.kDefaultBasicKeyboardLayout
}
if TISInputSource.generate(from: alphanumericalKeyboardLayout) == nil {
if !matchedResults.contains(where: { $0.identifier == alphanumericalKeyboardLayout }) {
alphanumericalKeyboardLayout = Self.kDefaultAlphanumericalKeyboardLayout
}
//

View File

@ -41,7 +41,7 @@ public extension UserDefRenderable<String> {
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
let theEntry = IMKHelper.allowedAlphanumericalTISInputSources[id]
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
Text(theEntry.titleLocalized).tag(theEntry.id)
}.id(UUID())
}
case (.string, .kBasicKeyboardLayout):
@ -49,7 +49,7 @@ public extension UserDefRenderable<String> {
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
let theEntry = IMKHelper.allowedBasicLayoutsAsTISInputSources[id]
if let theEntry = theEntry {
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
Text(theEntry.titleLocalized).tag(theEntry.id)
} else {
Divider()
}

View File

@ -41,7 +41,7 @@ public class UserDefRenderableCocoa: NSObject, Identifiable {
checkDef: switch def {
case .kAlphanumericalKeyboardLayout:
IMKHelper.allowedAlphanumericalTISInputSources.forEach { currentTIS in
objOptions.append((currentTIS.identifier, currentTIS.vChewingLocalizedName))
objOptions.append((currentTIS.id, currentTIS.titleLocalized))
}
optionsLocalizedAsIdentifiables = objOptions
case .kBasicKeyboardLayout:
@ -50,7 +50,7 @@ public class UserDefRenderableCocoa: NSObject, Identifiable {
objOptions.append(nil)
return
}
objOptions.append((currentTIS.identifier, currentTIS.vChewingLocalizedName))
objOptions.append((currentTIS.id, currentTIS.titleLocalized))
}
optionsLocalizedAsIdentifiables = objOptions
case .kKeyboardParser: