mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
TISCreateInputSourceList can return several entries for one input source id. A third-party IME publishes a non-selectable parent alongside the selectable mode, and taking sources.first can return the parent — after which TISSelectInputSource fails with paramErr (-50) while the caller reports success from the enable step. Found with Qingg (com.aodaren.inputmethod.Qingg), which exposes exactly that pair under one id. Its mode id equals the bundle id, so filtering by name would not have helped; selectability is the discriminator. Now filters on kTISPropertyInputSourceIsSelectCapable and falls back to the old behaviour when nothing advertises it, so single-entry sources are unaffected. Also enables every entry for the id rather than only the one being selected: selecting a mode whose parent is still disabled fails the same way. Compile-checked, and selecting com.apple.keylayout.ABC still exits 0. Unrelated to the enable path: on macOS 26.5.2 third-party IMEs are gated behind a consent sheet in System Settings. TISEnableInputSource returns noErr immediately regardless, and the enable only lands if that sheet is answered while the requesting process is still alive.
33 lines
1.2 KiB
Swift
33 lines
1.2 KiB
Swift
import Carbon
|
|
|
|
guard CommandLine.arguments.count == 2 else {
|
|
exit(2)
|
|
}
|
|
|
|
let properties = [kTISPropertyInputSourceID: CommandLine.arguments[1] as CFString] as CFDictionary
|
|
let sources = TISCreateInputSourceList(properties, true).takeRetainedValue() as! [TISInputSource]
|
|
guard !sources.isEmpty else {
|
|
exit(3)
|
|
}
|
|
|
|
func boolProperty(_ source: TISInputSource, _ key: CFString) -> Bool {
|
|
guard let raw = TISGetInputSourceProperty(source, key) else { return false }
|
|
return unsafeBitCast(raw, to: CFBoolean.self) == kCFBooleanTrue
|
|
}
|
|
|
|
// Why: one id can expose several entries — a third-party IME publishes a non-selectable
|
|
// parent alongside the selectable mode, and `sources.first` can return the parent, which
|
|
// then fails TISSelectInputSource with paramErr (-50). Qingg is the case that found this.
|
|
let selectable = sources.filter { boolProperty($0, kTISPropertyInputSourceIsSelectCapable) }
|
|
guard let source = selectable.first ?? sources.first else {
|
|
exit(3)
|
|
}
|
|
|
|
// Enable every entry for the id, not just the selected one: selecting a mode whose parent
|
|
// is still disabled also fails with -50.
|
|
for candidate in sources {
|
|
TISEnableInputSource(candidate)
|
|
}
|
|
|
|
exit(TISSelectInputSource(source) == noErr ? 0 : 4)
|