1

I'm trying to write a program that switches an input method on OS X. There is no problem with normal layouts. E.g. this code works (stripped away CFReleases/CFRetains for brevity):

TISInputSourceRef getref(char* id) {
    CFStringRef name     = CFStringCreateWithCString(kCFAllocatorDefault, id, kCFStringEncodingUTF8);
    CFStringRef keys[]   = { kTISPropertyLocalizedName };
    CFStringRef values[] = { name };
    CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 1, NULL, NULL);
    CFArrayRef array     = TISCreateInputSourceList(dict, true);
    return (TISInputSourceRef) CFArrayGetValueAtIndex(array, 0);
}

int main() {
    TISSelectInputSource(getref("U.S. International - PC"));
}

However when I select an input method instead of the layout:

TISSelectInputSource(getref("Hiragana"));

strange things happen: the IM is selected and visualized correctly in the menubar, however it is not enabled (typing produces Roman letters instead of Japanese). If I then switch to any other app and back the IM starts working correctly. The docs don't mention anything specific for this case. Please help me fix it and thanks in advance.

paulus
  • 637
  • 3
  • 9
  • possible duplicate of [change input source language programmatically OSx](http://stackoverflow.com/questions/6074757/change-input-source-language-programmatically-osx) – Chris Page Apr 26 '14 at 04:09
  • Thanks, Chris, but it's not the same. That question is basically "How do I switch an input source", and mine is "I know how to switch an input source, but why does it not switch when it is an input method rather than a simple layout". – paulus Apr 26 '14 at 06:49
  • Okay, I’ve retracted my vote to close. – Chris Page Apr 29 '14 at 12:17
  • What do you mean by “layout”? Do you mean a view? – Chris Page Apr 29 '14 at 12:17
  • I'm trying to use the terms from https://developer.apple.com/library/mac/documentation/TextFonts/Reference/TextInputSourcesReference/Reference/reference.html: "Keyboard input sources, including keyboard layouts, keyboard input methods and input modes". Basically what I mean under layout is "a letter that can be typed using a keyboard only, like 'a'". Under input method I mean "a letter that needs a special application to be typed, like 'あ', the latter uses the /System/Library/Input Methods/Kotoeri.app/Contents/MacOS/Kotoeri app loaded and applied to the input box". – paulus Apr 30 '14 at 08:19
  • Do not look up and select input sources using the localized name—it is merely the name displayed to the user, not a machine-usable identifier for reliably selecting an input source. Instead, use the input source identifier via kTISPropertyInputSourceID. The one you want is probably a variant of “com.apple.keyboardlayout.US”. – Chris Page May 01 '14 at 13:36
  • I have tried for sure. You may experience the thing yourself and try to enable Hiragana or Katakana in Kotoeri. The IM gets selected allright, it simply doesn't work. Here's the link to the apple support forum https://discussions.apple.com/message/25515409#25515409, and here's the link for the screencast of the problem: http://paulus.ru/static/hiragana.swf And thank you for trying to help :) – paulus May 01 '14 at 18:30
  • Oh! You’re trying to change the input method to affect *another process*. Typically these APIs are used by an application to select an IM for itself or one of its documents. If there’s a supported means to do this, it probably involves sending a notification to other processes. Also, beware of the interaction with the “Automatically switch to a document’s input source” system preference, which may affect your results. – Chris Page May 03 '14 at 00:48
  • Whow, can you be a bit more specific about notifications? Why is English selected okay without them? – paulus May 03 '14 at 12:40

1 Answers1

1

This answer is showing a programmatical UI operation method to switch input sources on macOS Big Sur / macOS 11 or later.

First, use the following Swift function with Applescript to get names of current input sources:

func UseApplescriptToGetSystemInputSourcesInMenubar() -> [InputSource] {
    let applesript = """
    tell application "System Events"
        tell process "TextInputMenuAgent"
            get the name of menu item of menu 1 of menu bar item 1 of menu bar 2
        end tell
    end tell
    """

    if let script = NSAppleScript(source: applesript) {
        var error: NSDictionary?
        let descriptor = script.executeAndReturnError(&error)
        /// descriptor: <NSAppleEventDescriptor: [ 'utxt'("Pinyin - Simplified"), 'utxt'("ABC"), 'utxt'("Hiragana"), 'msng', 'utxt'("Handwriting - Simplified"), 'msng', 'utxt'("Show Emoji & Symbols"), 'utxt'("Show Keyboard Viewer"), 'msng', 'utxt'("Show Input Source Name"), 'msng', 'utxt'("Open Keyboard Preferences…") ]>
        if let err = error {
            print(Time() + "[Applescript] NSAppleScript.executeAndReturnError(): \(err)")
        } else {
            var currentInputSources: [InputSource] = []
            for i in 1 ... descriptor.numberOfItems {
                if let inputSource = descriptor.atIndex(i)?.stringValue {
                    currentInputSources.append(InputSource(name: inputSource, id: i))
                } else {
                    print(Time() + "[Applescript] Got \(currentInputSources.count) input sources from menu bar.")
                    return currentInputSources
                }
            }
        }
    } else {
        print(Time() + "[Applescript] NSAppleScript.init()")
    }
    return []
}

Then use this Swift function to change input source:

func UseApplescriptToSwitchInputSource(to inputSourceName: String) {
    let applesript = """
    tell application "System Events"
        tell process "TextInputMenuAgent"
            click menu item "\(inputSourceName)" of menu 1 of menu bar item 1 of menu bar 2
            click menu bar item 1 of menu bar 2
        end tell
    end tell
    """

    if let script = NSAppleScript(source: applesript) {
        var error: NSDictionary?
        script.executeAndReturnError(&error)
        if let err = error {
            print(Time() + "[Applescript] NSAppleScript.executeAndReturnError(): \(err)")
        }
    } else {
        print(Time() + "[Applescript] NSAppleScript.init()")
    }
}

I have used the method above to develop an app using shortcuts to change input sources. You could check it on GitHub - https://github.com/Yang-Xijie/InputSourceSwitcher. In the README of that repo you can find pros and cons of my method and other methods to change input sources.

Yang_____
  • 117
  • 8