IMKHelper // Fix a TISInputSource installation crash in macOS 10.9.
This commit is contained in:
parent
cfad082b14
commit
df5075972a
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
// 其它多元選項參數自動糾錯。
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue