Open and Close Applications When an Other Launches or Terminates

- - posted in osx, programming, tools | Comments

A real programmer is lazy, right? Every now and then you find yourself doing a repetitive tasks consisting of a few small steps. The steps are fast to perform, but still… it just feels… wrong. It should be automated! Thanks to AppleScript this is an “easy” task. I will show my approach and problems I found.

The task to automate was my Matlab-workflow. I had two applications which needed to be opened and closed. First, my license of Matlab requires me to setup a VPN-connection to my university. Only then the Matlab application can load successfully. After using Matlab there is no need for the connection, so every time I am done with it, I have to terminate the VPN-client.

Second, there is a strange and irritating problem with Matlab and applications that use the Mac OS X support for assistive devices, such as (in my case) Flexiglass. When the checkbox in System Preferences > Accessibility > Enable access for assistive devices is checked, there is somewhere on your system an application that requires it. For me this was Flexiglass, and it produced an Java error in Matlab on almost any GUI action (Although is seems that this only occurs in a multi-monitor setup).

So when opening Matlab, first I would need to close Flexiglass. After the usage, I would open it again. Clearly this are needless steps for any programmer, so I searched a bit for this problem and found a helpful starting point in the following StackOverflow question: Close App A when App B closes: Mac OS X 10.7.3. It shows that AppleScript (which is a pretty funny language) is able to work with the Cocoa framework. This enables us to register for launching and termination of applications, using the shared instance of the NSWorkspace class:

Register for notifications
1
2
3
4
tell (pNSWorkspace's sharedWorkspace())'s notificationCenter()
  addObserver_selector_name_object_(me, "appQuitNotification:", "NSWorkspaceDidTerminateApplicationNotification", missing value)
  addObserver_selector_name_object_(me, "appLaunchNotification:", "NSWorkspaceDidLaunchApplicationNotification", missing value)
end tell

Once a notification is received, we would like to open and close applications. This is pretty easy:

Handle open and close notifications
1
2
3
4
5
6
7
8
9
10
11
12
13
14
on appLaunchNotification_(notification)
  set theLauchedApplication to (notification's userInfo's NSWorkspaceApplicationKey's localizedName()) as text
  if theLauchedApplication is in pTriggerLaunchApplications then
    -- Open the associated applications
    repeat with applicationToOpen in pOpenOnLaunchApplications
      tell application applicationToOpen to activate
    end repeat

    -- Close the associated applications
    repeat with applicationtoClose in pCloseOnLaunchApplication
      tell application applicationtoClose to quit
    end repeat
  end if
end appLaunchNotification_

Here you see I make use of a list of pTriggerLaunchApplications to check if the launched application should trigger actions. If so, it traverses the list of applications to open, and to quit. Notice the simplicity of the commands. Really nice!

The actions to perform when the trigger applications is terminated are slightly more complicated. When Flexiglass is opened, it shows the preferences window/pane. This is useless, and I want that window to be hidden when it is opened. The normal command would be:

Close all opened windows of an application
1
tell application "Flexiglass" to close every window

(Again, the simplicity!) Unfortunately, Flexiglass does not respond to AppleScript actions (there is no dictionary for it). The error you can expect is something like execution error: Flexiglass got an error: every window doesn't understand the close message. (-1708) This problem is solvable, by modifying the Info.plist file of the application; the flag NSAppleScriptEnabled YES needs to be added. Here is a nice explanation about how to add AppleScript support to an application. As that blogpost shows, different actions are needed depending on the Mac OS X version. The following checks for this and thereby support OS X 10.6, 10.7 and 10.8.

Enable AppleScript support to any applicationExplanation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
on enableAppleScripting(theApplication)
  -- Add AppleScript support to an application by overwriting the Info.plist in
  -- the Application bundle.
  -- See http://c-command.com/blog/2009/12/28/capture-from-preview/

  try
    set application_path to (path to application theApplication)
    set bundle_identifier to get bundle identifier of (info for the application_path)

    tell application "Finder"
      set the application_to_modify to (application file id bundle_identifier) as alias
    end tell

    set the app_path to (POSIX path of the application_to_modify)
    set the app_info_path to ((POSIX path of the application_to_modify) & "Contents/Info")
    set the plist_filepath to the quoted form of the app_info_path

    -- determine which Mac OS X version currently running
    set osver to system version of (system info)
    -- Make a backup of the Application bundle and overwrite the plist file
    do shell script "ditto -c -k --sequesterRsrc --keepParent " & app_path & space & app_path & ".quit-open.zip" with administrator privileges
    do shell script "defaults write " & app_info_path & space & "NSAppleScriptEnabled -bool YES" with administrator privileges
    do shell script "chmod a+r" & space & app_info_path & ".plist" with administrator privileges

    if osver >= "10.7" then
      if osver >= "10.8" then
        -- Assume Xcode is installed
    do shell script "sudo ln -s /Applications/Xcode.app/Contents/Developer/usr/bin/codesign_allocate /usr/bin" with administrator privileges
      end if
      do shell script "codesign -f -s - " & app_path with administrator privileges
    end if
  on error message number errorNumber
    -- Something went wrong
    if message is not equal to "ln: /usr/bin/codesign_allocate: File exists" then
      display dialog "Problem with enabling AppleScript for " & theApplication & ": " & message & " -- Error number: " & errorNumber
    end if
  end try
end enableAppleScripting

This function uses shell commands to modify the application’s info.plist. It assumes an installation of Xcode (on Mac OS X 10.8) and requires administrator privileges. This block is only executed the first time it tries to hide the windows of Flexiglass. Along the way is creates and backup zip in the /Applications directory.

The result of this AppleScript is an App Bundle. You can make your own version by using the following gist, and open this is the AppleScript editor (by pasting it after AppleScript Editor > File > New from Template > Cocoa-AppleScript applet). The bundle can be placed in /Applications, and to automatically start it, go to System Preferences > Users & Groups > Login Items and add the application. Enable the checkbox to start it hidden. Note the configuration properties in the script, which you can modify to your situation.

To keep the app from from dock, add the following after the first in the Info.plist of your created App bundle:

Add to Info.plist of App bundle
1
2
<key>NSUIElement</key>
<string>1</string>

Please feel free to use, reuse and modify the code to your needs. You can find it in my Quit-Open repository. It helped me with a silly repetitive task and I am certain I will use AppleScript in more occasions, since it is pretty easy and is really powerful!

Comments