Switch to a specific input source in OS X in Catalina with AppleScript
I use different keyboard layouts and switched between them using a simple script
on changeKeyboardLayout(layoutName)
tell application "System Events" to tell process "SystemUIServer"
tell (1st menu bar item of menu bar 1 whose description is "text input") to {click, click (menu 1's menu item layoutName)}
end tell
end changeKeyboardLayout
changeKeyboardLayout("German LaTeX2")
That worked well until the latest update. I don't really speak AppleScript, but a bit of testing indicates that SystemUIServer no longer has access to a menu item with name "text input". Trying
tell application "System Events" to tell process "SystemUIServer" to get value of attribute "AXDescription" of every menu bar item of menu bar 1
gives me Siri, blutooth, time machine, clock
and a few others. So somehow the input source menu is managed differently now, but I have now clue how or how to find out (all searches pointed me to scripts similar to mine)
Update 2019/12/10
The suggested solutions so far both have defects, in one case you always get a delay of 5 seconds which is rather long and the trick with killing "System Events" as taken from the other posting doesn't always seem to work, sometimes it results in the script getting a timeout (which is even worse then have the 5 seconds delay all the time), so it looks as if this is still in need for a better solution
I stumbled upon the same issue of wanting to switch between multiple input sources with keyboard shortcuts quickly since Catalina broke the old method using AppleScript. Although the above methods work, they're rather clunky, unreliable, and slow (at least for me).
After a bit of digging around the internet I found a better solution. Someone wrote a terminal / command line utility program that changes input source layouts by name that works really well, and changes input sources instantaneously (tested in Catalina).
Here are the steps to make it work:
- Download the source files from https://github.com/minoki/InputSourceSelector - click on the green button Code → Download ZIP
- Extract the ZIP file
- Open terminal and navigate to the unzipped folder
- Run the
make
command in that folder, and an executable "InputSourceSelector" will be produced. (You may have to add executable permissions to it viachmod +x InputSourceSelector
).
Now you can list and switch input sources with these commands:
-
./InputSourceSelector list-enabled
- to list all currently enabled input sources -
./InputSourceSelector current
- to show the currently selected input source id -
./InputSourceSelector select <InputSourceId>
- to switch to an input source by its id
See the README on the author's page for more commands.
Then to switch layouts, use the command 'select' with an <InputSourceId>
:
./InputSourceSelector select com.apple.keylayout.US
./InputSourceSelector select com.apple.keylayout.Dvorak
./InputSourceSelector select com.apple.keylayout.Spanish
The <InputSourceId>
is the id before the parentheses, for example:
- For "com.apple.keylayout.US (U.S.)" the id is "com.apple.keylayout.US"
- For "com.apple.keylayout.Dvorak (Dvorak)" the id is "com.apple.keylayout.Dvorak"
- For "com.apple.keylayout.Spanish (Spanish)" the id is "com.apple.keylayout.Spanish"
For ids with spaces, you may have to enclose them in double quotes.
You can now place the executable file in '/usr/local/bin' to make it available system-wide, and run the command without the preceding './' from anywhere in the system.
You can also make a couple shortcuts to quickly switch between the various input sources in BetterTouchTool or a similar program.
In BetterTouchTool just add a global keyboard shortcut, and for the Assigned Action select "Execute Terminal Command". In the terminal command window use the command /usr/local/bin/InputSourceSelector select com.apple.keylayout.Dvorak
or whatever input source you want to switch to.
Unfortunately things have changed in macOS Catalina and you''ll have to activate that menu in a different manner.
The following example AppleScript code, shown further below, works for me in macOS Catalina, however as I was testing in a virtual machine I do not know if the time lag in the running of the code, between clicking the target menu and the subsequent code, is inherent of an issue in the virtual machine or it will be the same on a physical machine.
I do not have German LaTeX2 so I used German instead, although for convenience I have replaced it in the code after testing.
The usual caveats apply for the use of this in: System Preferences > Security & Privacy > Privacy > Accessibility
Example AppleScript code:
tell application "System Events" to ¬
tell application process "TextInputMenuAgent" to ¬
tell menu bar item 1 of menu bar 2
click
click menu item "German LaTeX2" of menu 1
end tell
This does the same thing in a different manner, and one of these ways may be faster.
tell application "System Events" to ¬
tell application process "TextInputMenuAgent" to ¬
click menu bar item 1 of menu bar 2
tell application "System Events"
keystroke "g"
key code 36
end tell
To use the example AppleScript code in a handler:
changeKeyboardLayout("German LaTeX2")
on changeKeyboardLayout(layoutName)
tell application "System Events" to ¬
tell application process "TextInputMenuAgent" to ¬
tell menu bar item 1 of menu bar 2
click
click menu item layoutName of menu 1
end tell
end changeKeyboardLayout
Or the second version of the code:
changeKeyboardLayout("German LaTeX2")
on changeKeyboardLayout(layoutName)
tell application "System Events" to ¬
tell application process "TextInputMenuAgent" to ¬
click menu bar item 1 of menu bar 2
tell application "System Events"
keystroke (first text item of layoutName)
key code 36
end tell
end changeKeyboardLayout
Note: The example AppleScript code is just that and does not contain any error handling as may be appropriate. The onus is upon the user to add any error handling as may be appropriate, needed or wanted. Have a look at the try statement and error statement in the AppleScript Language Guide. See also, Working with Errors.
For the record, combing the answer how to get to the input source menu in Catalina with an answer that deals with the bug (?) generating a delay give a version that work as nicely as the old one did:
on changeKeyboardLayout(layoutName)
launch application "System Events"
delay 0.2
ignoring application responses
tell application "System Events" to tell process "TextInputMenuAgent"
click menu bar item 1 of menu bar 2
end tell
end ignoring
do shell script "killall System\\ Events"
delay 0.1
tell application "System Events" to tell process "TextInputMenuAgent"
tell menu bar item 1 of menu bar 2
click menu item layoutName of menu 1
end tell
end tell
end changeKeyboardLayout
changeKeyboardLayout("German LaTeX2")
Update 2019/12/10
The trick with killing "System Events" as taken from the other posting doesn't always seem to work, sometimes it results in the script getting a timeout (which is even worse then have the 5 seconds delay all the time), so it looks as if the solution to my question is still somewhat open
I wrote this script a while ago that uses a carbon API to change the keyboard input source through low-level system calls, which would ordinarily be ideal and obviously preferable to manipulating the UI. That said, it is a couple of years old, and so I couldn't say whether or not it will still be viable in Catalina, not least because carbon is being (or is) deprecated, but the script is written in JavaScript for Automation, which, like AppleScript, is basically falling to bits.
But, I'll post it anyway, just in case it does have value in the newer OS setting:
ObjC.import('Carbon');
nil = $();
unwrap = ObjC.deepUnwrap.bind(ObjC);
bind = ObjC.bindFunction.bind(ObjC);
bind('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
Ref.prototype.nsObject = function() {
return unwrap($.CFMakeCollectable(this));
}
function run() {
const lang = 'German';
const currentSource = $.TISCopyCurrentKeyboardInputSource();
const sources = unwrap($.TISCreateInputSourceList(nil, false)),
sourceIDs = sources.map(src => $.TISGetInputSourceProperty(
src,'TISPropertyInputSourceID')
.nsObject()),
sourceID = sourceIDs.filter(src=>src.indexOf(lang)!==-1)[0],
source = unwrap($.TISCreateInputSourceList(
{ 'TISPropertyInputSourceID' : sourceID },
false))[0];
const status = $.TISSelectInputSource(source);
return (status==0);
}
I have found an alternative solution that guarantees no delay, using the system-wide keyboard shortcut ^F8 (predefined in "System Preferences > Keyboard > Shortcuts > Keyboard > Move focus to status menus")
combined with the desired amount of down-arrow key-strokes and a Return, works like a charm
I wish the above looks like AppleScript for you, the fact is I still can't write the simplest AppleScript after years of using it.
So I defined the above series of key-strokes in BetterTouchTool, it does the job :)