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 {
|
else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return tsInputModeListKey.keys.compactMap { TISInputSource.generate(from: $0) }
|
return TISInputSource.match(modeIDs: tsInputModeListKey.keys.map(\.description))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSApp Activation Helper
|
// MARK: - NSApp Activation Helper
|
||||||
|
|
|
@ -27,7 +27,7 @@ public enum IMKHelper {
|
||||||
"com.apple.keylayout.Dvorak-Right",
|
"com.apple.keylayout.Dvorak-Right",
|
||||||
]
|
]
|
||||||
if #unavailable(macOS 10.13) {
|
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.German")
|
||||||
result.append("com.apple.keylayout.French")
|
result.append("com.apple.keylayout.French")
|
||||||
}
|
}
|
||||||
|
@ -49,33 +49,31 @@ public enum IMKHelper {
|
||||||
"org.unknown.keylayout.vChewingMiTAC",
|
"org.unknown.keylayout.vChewingMiTAC",
|
||||||
]
|
]
|
||||||
|
|
||||||
public static var allowedAlphanumericalTISInputSources: [TISInputSource] {
|
public static var allowedAlphanumericalTISInputSources: [TISInputSource.KeyboardLayout] {
|
||||||
arrWhitelistedKeyLayoutsASCII.compactMap { TISInputSource.generate(from: $0) }
|
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
|
||||||
|
return arrWhitelistedKeyLayoutsASCII.compactMap { allTISKeyboardLayouts[$0] }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource?] {
|
public static var allowedBasicLayoutsAsTISInputSources: [TISInputSource.KeyboardLayout?] {
|
||||||
// 為了保證清單順序,先弄兩個容器。
|
let allTISKeyboardLayouts = TISInputSource.getAllTISInputKeyboardLayoutMap()
|
||||||
var containerA: [TISInputSource?] = []
|
// 為了保證清單順序,先弄幾個容器。
|
||||||
var containerB: [TISInputSource?] = []
|
var containerA: [TISInputSource.KeyboardLayout?] = []
|
||||||
var containerC: [TISInputSource?] = []
|
var containerB: [TISInputSource.KeyboardLayout?] = []
|
||||||
|
var containerC: [TISInputSource.KeyboardLayout] = []
|
||||||
|
|
||||||
let rawDictionary = TISInputSource.rawTISInputSources(onlyASCII: false)
|
let filterSet = Array(Set(arrWhitelistedKeyLayoutsASCII).subtracting(Set(arrDynamicBasicKeyLayouts)))
|
||||||
|
let matchedGroupBasic = (arrWhitelistedKeyLayoutsASCII + arrDynamicBasicKeyLayouts).compactMap {
|
||||||
Self.arrWhitelistedKeyLayoutsASCII.forEach {
|
allTISKeyboardLayouts[$0]
|
||||||
if let neta = rawDictionary[$0], !arrDynamicBasicKeyLayouts.contains(neta.identifier) {
|
}
|
||||||
|
matchedGroupBasic.forEach { neta in
|
||||||
|
if filterSet.contains(neta.id) {
|
||||||
containerC.append(neta)
|
containerC.append(neta)
|
||||||
}
|
} else if neta.id.hasPrefix("com.apple") {
|
||||||
}
|
|
||||||
|
|
||||||
Self.arrDynamicBasicKeyLayouts.forEach {
|
|
||||||
if let neta = rawDictionary[$0] {
|
|
||||||
if neta.identifier.contains("com.apple") {
|
|
||||||
containerA.append(neta)
|
containerA.append(neta)
|
||||||
} else {
|
} else {
|
||||||
containerB.append(neta)
|
containerB.append(neta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 這裡的 nil 是用來讓選單插入分隔符用的。
|
// 這裡的 nil 是用來讓選單插入分隔符用的。
|
||||||
if !containerA.isEmpty { containerA.append(nil) }
|
if !containerA.isEmpty { containerA.append(nil) }
|
||||||
|
|
|
@ -12,8 +12,13 @@ import InputMethodKit
|
||||||
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
|
// MARK: - TISInputSource Extension by The vChewing Project (MIT-NTL License).
|
||||||
|
|
||||||
public extension TISInputSource {
|
public extension TISInputSource {
|
||||||
|
struct KeyboardLayout: Identifiable {
|
||||||
|
public var id: String
|
||||||
|
public var titleLocalized: String
|
||||||
|
}
|
||||||
|
|
||||||
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
static var allRegisteredInstancesOfThisInputMethod: [TISInputSource] {
|
||||||
TISInputSource.modes.compactMap { TISInputSource.generate(from: $0) }
|
TISInputSource.match(modeIDs: TISInputSource.modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var modes: [String] {
|
static var modes: [String] {
|
||||||
|
@ -22,7 +27,7 @@ public extension TISInputSource {
|
||||||
else {
|
else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return tsInputModeListKey.keys.map { $0 }
|
return tsInputModeListKey.keys.map(\.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult static func registerInputMethod() -> Bool {
|
@discardableResult static func registerInputMethod() -> Bool {
|
||||||
|
@ -80,10 +85,6 @@ public extension TISInputSource {
|
||||||
== kCFBooleanTrue
|
== kCFBooleanTrue
|
||||||
}
|
}
|
||||||
|
|
||||||
static func generate(from identifier: String) -> TISInputSource? {
|
|
||||||
TISInputSource.rawTISInputSources(onlyASCII: false)[identifier]
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputModeID: String {
|
var inputModeID: String {
|
||||||
unsafeBitCast(TISGetInputSourceProperty(self, kTISPropertyInputModeID), to: NSString.self) as 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
|
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 辭典。
|
// 為了指定檢索條件,先構築 CFDictionary 辭典。
|
||||||
// 第二項代指辭典容量。
|
|
||||||
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
|
let dicConditions: [CFString: Any] = !onlyASCII ? [:] : [
|
||||||
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
|
kTISPropertyInputSourceType: kTISTypeKeyboardLayout as CFString,
|
||||||
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
|
kTISPropertyInputSourceIsASCIICapable: kCFBooleanTrue as CFBoolean,
|
||||||
|
@ -132,10 +158,21 @@ public extension TISInputSource {
|
||||||
if onlyASCII {
|
if onlyASCII {
|
||||||
result = result.filter { $0.scriptCode == 0 }
|
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 {
|
result.forEach {
|
||||||
resultDictionary[$0.inputModeID] = $0
|
let newNeta1 = TISInputSource.KeyboardLayout(id: $0.inputModeID, titleLocalized: $0.vChewingLocalizedName)
|
||||||
resultDictionary[$0.identifier] = $0
|
let newNeta2 = TISInputSource.KeyboardLayout(id: $0.identifier, titleLocalized: $0.vChewingLocalizedName)
|
||||||
|
resultDictionary[$0.inputModeID] = newNeta1
|
||||||
|
resultDictionary[$0.identifier] = newNeta2
|
||||||
}
|
}
|
||||||
return resultDictionary
|
return resultDictionary
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,11 @@ public extension PrefMgr {
|
||||||
keyboardParser = 0
|
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
|
basicKeyboardLayout = Self.kDefaultBasicKeyboardLayout
|
||||||
}
|
}
|
||||||
if TISInputSource.generate(from: alphanumericalKeyboardLayout) == nil {
|
if !matchedResults.contains(where: { $0.identifier == alphanumericalKeyboardLayout }) {
|
||||||
alphanumericalKeyboardLayout = Self.kDefaultAlphanumericalKeyboardLayout
|
alphanumericalKeyboardLayout = Self.kDefaultAlphanumericalKeyboardLayout
|
||||||
}
|
}
|
||||||
// 其它多元選項參數自動糾錯。
|
// 其它多元選項參數自動糾錯。
|
||||||
|
|
|
@ -41,7 +41,7 @@ public extension UserDefRenderable<String> {
|
||||||
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
|
Picker(LocalizedStringKey(metaData.shortTitle ?? ""), selection: binding) {
|
||||||
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
|
ForEach(0 ... (IMKHelper.allowedAlphanumericalTISInputSources.count - 1), id: \.self) { id in
|
||||||
let theEntry = IMKHelper.allowedAlphanumericalTISInputSources[id]
|
let theEntry = IMKHelper.allowedAlphanumericalTISInputSources[id]
|
||||||
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
|
Text(theEntry.titleLocalized).tag(theEntry.id)
|
||||||
}.id(UUID())
|
}.id(UUID())
|
||||||
}
|
}
|
||||||
case (.string, .kBasicKeyboardLayout):
|
case (.string, .kBasicKeyboardLayout):
|
||||||
|
@ -49,7 +49,7 @@ public extension UserDefRenderable<String> {
|
||||||
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
|
ForEach(0 ... (IMKHelper.allowedBasicLayoutsAsTISInputSources.count - 1), id: \.self) { id in
|
||||||
let theEntry = IMKHelper.allowedBasicLayoutsAsTISInputSources[id]
|
let theEntry = IMKHelper.allowedBasicLayoutsAsTISInputSources[id]
|
||||||
if let theEntry = theEntry {
|
if let theEntry = theEntry {
|
||||||
Text(theEntry.vChewingLocalizedName).tag(theEntry.identifier)
|
Text(theEntry.titleLocalized).tag(theEntry.id)
|
||||||
} else {
|
} else {
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class UserDefRenderableCocoa: NSObject, Identifiable {
|
||||||
checkDef: switch def {
|
checkDef: switch def {
|
||||||
case .kAlphanumericalKeyboardLayout:
|
case .kAlphanumericalKeyboardLayout:
|
||||||
IMKHelper.allowedAlphanumericalTISInputSources.forEach { currentTIS in
|
IMKHelper.allowedAlphanumericalTISInputSources.forEach { currentTIS in
|
||||||
objOptions.append((currentTIS.identifier, currentTIS.vChewingLocalizedName))
|
objOptions.append((currentTIS.id, currentTIS.titleLocalized))
|
||||||
}
|
}
|
||||||
optionsLocalizedAsIdentifiables = objOptions
|
optionsLocalizedAsIdentifiables = objOptions
|
||||||
case .kBasicKeyboardLayout:
|
case .kBasicKeyboardLayout:
|
||||||
|
@ -50,7 +50,7 @@ public class UserDefRenderableCocoa: NSObject, Identifiable {
|
||||||
objOptions.append(nil)
|
objOptions.append(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objOptions.append((currentTIS.identifier, currentTIS.vChewingLocalizedName))
|
objOptions.append((currentTIS.id, currentTIS.titleLocalized))
|
||||||
}
|
}
|
||||||
optionsLocalizedAsIdentifiables = objOptions
|
optionsLocalizedAsIdentifiables = objOptions
|
||||||
case .kKeyboardParser:
|
case .kKeyboardParser:
|
||||||
|
|
Loading…
Reference in New Issue