2

I want to automate the keyboard layout based on the application. I am using an Windows VM over remote I wan't to create a script which can:

  1. Change the Keyboard layout when the application switches to Windows VM
  2. Change it back to normal layout with all other apps.

I have hard time finding a trigger event on application focus change. Is it possible to do it over automator?

Or else kindly suggest another solution to resolve the issue.

Arun Doss
  • 45
  • 8
  • Perhaps **NSWorkspace**'s `NSWorkspaceDidActivateApplicationNotification` and `NSWorkspaceDidDeactivateApplicationNotification` notifications via some AppleScriptObjC? – red_menace Oct 22 '20 at 13:49

1 Answers1

3

Assuming that you only have two keyboard layouts, you can use the following script application.

First, you'll need to open System Preferences and look at Keyboard➔Shortcuts➔Input Sources; see what the what the keyboard shortcut for 'selecting previous input source' is, make sure it's clicked on, and copy it into the script at the marked places. I have mine set to comd-ctrl-space (^⌘ ), but yours is likely different. You should also check that "Windows VM" is the correct name for the app: find the application in the Finder, select it and type ⌘I to open a Get Info window, and look in the Name & Extension text box.

Now do the following

  1. Copy the following script in the Script Editor
  2. Save it as a stay-open application
    • select 'Application' from the 'Format' menu at the bottom left
    • click the 'Stay open after run handler' checkbox
  3. Open System Preferences and do the following
    • add the app to the Accessibility section in Security & Privacy➔Privacy (so it can send keyboard shortcuts)
    • add the app to the Login Items section for your account in Users & Groups (so it starts automatically at login)

You can then run it and test it out. The app waits in the background to receive notifications that an app has activated or deactivated, then it checks the app name and triggers the keyboard shortcut for the switching the input source when the Window VM app comes or goes.

use AppleScript version "2.4"
use framework "AppKit"
use scripting additions

property NSWorkspace : class "NSWorkspace"

on run
    set workSp to NSWorkspace's sharedWorkspace()
    set notifCent to workSp's notificationCenter()
    tell notifCent to addObserver:me selector:"appHasActivated:" |name|:"NSWorkspaceDidActivateApplicationNotification" object:(missing value)
    tell notifCent to addObserver:me selector:"appHasDeactivated:" |name|:"NSWorkspaceDidDeactivateApplicationNotification" object:(missing value)
end run

on idle
    -- we don't use the idle loop, so tell the system to check in once an hour
    return 3600
end idle

on appHasActivated:notif
    set targetApp to (notif's userInfo's valueForKey:"NSWorkspaceApplicationKey")
    set targetAppName to (targetApp's localizedName) as text
    if targetAppName is "Windows VM" then
        tell application "System Events"
            tell process "Windows VM"
                -- change this line to match your 'Select previous input source' keyboard shortcut
                keystroke space using {command down, control down}
            end tell
        end tell
    end if
end appHasActivated:

on appHasDeactivated:notif
    set targetApp to (notif's userInfo's valueForKey:"NSWorkspaceApplicationKey")
    set targetAppName to (targetApp's localizedName) as text
    if targetAppName is "Windows VM" then
        tell application "System Events"
            tell process targetAppName
                -- change this line to match your 'Select previous input source' keyboard shortcut
                keystroke space using {command down, control down}
            end tell
        end tell
    end if
end appHasDeactivated:

Once you are satisfied it works, you can run the following command in terminal:

defaults write '/path/to/$name.app/Contents/Info.plist' LSUIElement -bool yes

This will turn the app into a background agent, which never takes the foreground and is invisible in the dock. That way you can just forget about it.

Ted Wrigley
  • 2,921
  • 2
  • 7
  • 17
  • 1
    Thanks Ted. Works as expected. Could you suggest me a material to improve my AppleScript skills? – Arun Doss Oct 23 '20 at 04:41
  • @ArunDoss: I'd suggest you start [MacOSXAutomation](https://macosxautomation.com/applescript/learn.html). it's a good overview for beginner and intermediate AppleScripters, with some peeks at more powerful stuff. After that, you can look over the forums at https://macscripter.net, where the power-users hide out. But really, the best way to learn any scripting language is to set yourself a project and keep failing until you succeed. – Ted Wrigley Oct 23 '20 at 04:51
  • @ArunDoss: Oh, and D'oh! Apple's own [AppleScript Language Guide](https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html), a must-have. Apple used to offer a downloadable pdf version, but I can't find it anymore. – Ted Wrigley Oct 23 '20 at 04:56
  • Personally I use [EventScripts](https://www.mousedown.net/software/EventScripts.html) with a binary created from the _code_ in [this answer](https://stackoverflow.com/questions/23729704/change-osx-keyboard-layoutinput-source-programmatically-via-terminal-or-appl/63232278#63232278) that allows passing a _command line parameter_. I tested your _code_ and found it easy to get out of sync because it's just toggling using **System Events** and a _keyboard shortcut_. I would have upvoted this if it had used **AppleScriptObjC** to perform a change with a parameter vs toggling with **System Events**. – user3439894 Oct 23 '20 at 05:24
  • @user3439894: I didn't really want to get into carbon core (which is just gibberish to anyone not a developer), not when there's a perfectly adequate (and far more accessible) AppleScript solution. This is scripting, not rocket science; why over-engineer? – Ted Wrigley Oct 23 '20 at 14:23
  • Ironically **AppleScript** too can be just as gibberish to many users that have never done any programming or scripting. The issue I see here is that using **System Events** and a _keyboard shortcut_ is not stable when toggling between two items as I can easily and consistently throw the sequence out of wack. It doesn't need to be over engineered, just better then a MacGyver, which is what **System Events** and a _keyboard shortcut_ is in this use case and why I use **EventScripts** and its **Application activated/deactivated** _events_ to run a _binary_ that take a _parameter_. – user3439894 Oct 23 '20 at 14:38
  • @user3439894: There are only three ways for the process to get out of whack. (1) initially start the background app with Windows VM in the foreground. (2) accidentally toggle the keyboard shortcut. (3) cycle through apps so rapidly that notifications get lost in delivery. If any of those become a consistent problem, we'll take it up again and fix it. If we *try* to break the pattern it's easy to do, but in normal usage it's unlikely to happen. – Ted Wrigley Oct 23 '20 at 14:49
  • @user3439894: in any case, I'd encourage you to write your own answer. You're not offering constructive criticism to my answer; you're suggesting an entirely different approach, which Arun might want to see explicitly. – Ted Wrigley Oct 23 '20 at 14:51
  • 1
    Ted Wrigley, I am not intensionally trying to criticize your answer and frankly when I said "I would have upvoted this if it had used **AppleScriptObjC** to perform a change with a parameter vs toggling with **System Events.**" that was IMO constructive criticism. Your answer is a _good_ answer, just not _good_ enough IMO and a _better_ answer would have avoided **System Events** and a _keyboard shortcut_. I actually had started writing an answer before you posted yours, but got distracted with other things and obviously never posted it, maybe I will. – user3439894 Oct 23 '20 at 15:21
  • @TedWrigley: Thanks for the recommendation. The [MacOSXAutomation](https://macosxautomation.com/applescript/learn.html) site was very helpful and interesting. I will also follow your other suggestions on my os automation journey. – Arun Doss Oct 28 '20 at 02:11