简体   繁体   中英

Change OSX keyboard layout("input source") programmatically via terminal or AppleScript?

I am currently switching input sources by running a GUI AppleScript through Alfred, and the GUI script can sometime take up to 1s to complete the change. It gets quite annoying at times.

I have come across Determine OS X keyboard layout (“input source”) in the terminal/a script . And I want to know since we can find out the current input source if there's a way to change input source programatically ? I'd tried overwriting the com.apple.HIToolbox.plist but it does not change the input.

(I do realise there's mapping shortcut to input sources available in the system preference, however I prefer mapping keywords with Alfred)

You can do it using the Text Input Services API:

NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : @"com.apple.keylayout.French" }, FALSE));
TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
OSStatus status = TISSelectInputSource(source);
if (status != noErr)
    /* handle error */;

The dictionary in the first line can use other properties for other criteria for picking an input source.

There's also NSTextInputContext . It has a selectedKeyboardInputSource which can be set to an input source ID to select a different input source. The issue there is that you need an instance of NSTextInputContext to work with and one of those exists only when you have a key window with a text view as its first responder.

@Ken Thomases' solution is probably the most robust - but it requires creation of a command-line utility.

A non-GUI-scripting shell scripting / AppleScripting solution is unfortunately not an option : while it is possible to update the *.plist file that reflects the currently selected input source (keyboard layout) - ~/Library/Preferences/com.apple.HIToolbox.plist - the system will ignore the change.

However, the following GUI-scripting solution (based on this ), while still involving visible action, is robust and reasonably fast on my machine (around 0.2 seconds):

(If you just wanted to cycle through installed layouts, using a keyboard shortcut defined in System Preferences is probably your best bet; the advantage of this solution is that you can target a specific layout.)

Note the prerequisites mentioned in the comments.

# Example call
my switchToInputSource("Spanish")

# Switches to the specified input source (keyboard layout) using GUI scripting.
# Prerequisites:
#   - The application running this script must be granted assisistive access.
#   - Showing the Input menu in the menu bar must be turned on 
# (System Preferences > Keyboard > Input Sources > Show Input menu in menu bar).
# Parameters:
#    name ... input source name, as displayed when you open the Input menu from
#             the menu bar; e.g.: "U.S."
# Example:
#   my switchToInputSource("Spanish")
on switchToInputSource(name)
    tell application "System Events" to tell process "SystemUIServer"
        tell (menu bar item 1 of menu bar 1 whose description is "text input")
            # !! Sadly, we must *visibly* select (open) the text-input menu-bar extra in order to
            # !! populate its menu with the available input sources.
            select
            tell menu 1
                # !! Curiously, using just `name` instead of `(get name)` didn't work: 'Access not allowed'.
                click (first menu item whose title = (get name))
            end tell
        end tell
    end tell
end switchToInputSource

Solution using Xcode Command Line Tools

For those, who would like to build @Ken Thomases' solution but without installing Xcode (which is several GiB and is totally useless to spend so much space on unless used seriously) it is possible to build it using the Xcode Command Line Tools.

There are several tutorials on the internet about how to install Xcode Command Line Tools. The point here is only that it takes fraction of the space compared to full-blown Xcode.

Once you have it installed, these are the steps:

  1. Create a file called whatever.m
  2. In whatever.m put the following:
#include <Carbon/Carbon.h>

int main (int argc, const char * argv[]) {
    NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : @"com.apple.keylayout.French" }, FALSE));
    TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
    OSStatus status = TISSelectInputSource(source);
    if (status != noErr)
        return -1;

    return 0;
}
  1. Replace French with your desired layout.
  2. Save the file
  3. Open terminal in the same folder as whatever.m is
  4. Run this command: clang -framework Carbon whatever.m -o whatever

Your application is created as whatever in the same folder and can be executed as: .\whatever

Additionally

I've never created any Objective-C programs, so this may be suboptimal, but I wanted an executable that can take the keyboard layout as a command line parameter. For anyone interested, here's the solution I came up with:

In step 2 use this code:

#import <Foundation/Foundation.h>
#include <Carbon/Carbon.h>

int main (int argc, const char * argv[]) {
    NSArray *arguments = [[NSProcessInfo processInfo] arguments];

    NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : [@"com.apple.keylayout." stringByAppendingString:arguments[1]] }, FALSE));
    TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
    OSStatus status = TISSelectInputSource(source);
    if (status != noErr)
        return -1;

    return 0;
}

In step 6. run this command: clang -framework Carbon -framework Foundation whatever.m -o whatever

You can now switch to any layout from the command line, eg: ./whatever British

Note: it only allows to switch to layouts already configured on your system!

Another option is to use Swift. It can be used in a script-like fashion (no compilation).

  • Install Xcode Command Line Tools
  • Create a script from the code below
  • Run the script using swift script_file_name

Code:

import Carbon

let command = ProcessInfo.processInfo.arguments.dropFirst().last ?? ""
let filter = command == "list" ? nil : [kTISPropertyInputSourceID: command]

guard let cfSources = TISCreateInputSourceList(filter as CFDictionary?, false),
      let sources = cfSources.takeRetainedValue() as? [TISInputSource] else {
    print("Use \"list\" as an argument to list all enabled input sources.")
    exit(-1)
}

if filter == nil { // Print all sources
    print("Change input source by passing one of these names as an argument:")
    sources.forEach {
        let cfID = TISGetInputSourceProperty($0, kTISPropertyInputSourceID)!
        print(Unmanaged<CFString>.fromOpaque(cfID).takeUnretainedValue() as String)
    }
} else if let firstSource = sources.first { // Select this source
    exit(TISSelectInputSource(firstSource))
}

This elaborates on answers by Ken Thomases and sbnc.eu .

On AppleScript you must only take cmd + "space" (or something other, what you use for change keyboard source).

And all what you need:

    key code 49 using command down

49 - code of 'space' button in ASCII for AppleScript.

PS: don't forget get access for you AppleScript utility in System Preferences.

tell application "System Events"
    key code 49 using control down
end tell

Changes layout via keypress

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM