Monthly Archives: August 2018

Apple Events Usage Description

In case you haven’t heard, macOS Mojave is bringing a new “Apple Events sandbox” that will affect the behavior of apps that send Apple Events to other apps either directly, or by way of running an AppleScript. I wrote more about this on my non-development blog, Bitsplitting: Reauthorizing Automation in Mojave.

Typically, the system is simply supposed to ask users whether they approve of the Apple Events being sent from one app to another, but in some instances it seems the events are rejected without ever prompting the user. My friend Paul Kim of Hazel fame asked in a developer chat room whether any of us were having this kind of trouble specifically with apps built in Xcode 10, against the macOS 10.14 SDK.

This rang a bell for me on two levels: first, I had seen a similar behavior with FastScripts, which I eventually fixed by switching it to a much newer build system, and dropping support for versions of macOS older than 10.12. It was the right time for me to make those changes, so I didn’t mind doing it, but I never quite understood why the behavior was happening.

I noticed after building and running on my developer Mac (which is running the 10.14 beta), I was still experiencing the behavior Paul described. With FastScripts, these kinds of alerts pop up all over the place, because by virtue of running scripts, users are often incidentally sending Apple Events to other apps. Here’s a simple example script that I can run via FastScripts:

tell app "Preview"
    get document 1
end tell

When I run this with a version of FastScripts that was linked against the 10.14 SDK, I get this error message from FastScripts itself. The system never prompts to grant permission:

Scripting error received when attempting to run a script from FastScripts

Until Paul mentioned his own problems, I glossed over these failures because I was satisfied that my production built versions, linked against the 10.13 SDK, were “working fine.” But Paul’s report got me thinking: was it possible there is some unspoken contract here, whereby linking against the 10.14 SDK opens up my app to additional privacy related requirements?

I tapped into Xcode’s Info.plist editor for FastScripts, added a new field, and typed “Privacy” on a hunch, because I’ve come to realize that Apple prefixes the plain-English description for most, if not all, of their “usage explanation” Info.plist fields with this word:

Screenshot of the list of Privacy-related Info.plist strings that appears

Aha, that first one looks promising. You can right-click on an Info.plist string in Xcode to “Show Raw Keys/Values”, and doing so reveals that the Info.plist key in question is “NSAppleEventsUsageDescription”. After adding the key to my app, I built and run again, and running the same script as above now yields the expected authorization panel:

Screenshot of Apple's standard panel requesting access for FastScripts to control another app.

I’m not sure if requiring the usage description string is intentional or not, but it’s probably a good idea even if, as in the case of FastScripts, you have to be pretty vague about what the specific usage is. I don’t think this usage string was covered in the WWDC 2018 session about macOS security. Hopefully if you’ve run into this with your Mac app, this post will help you to work around the problem.

Let it Rip

In the latest Mojave public beta, I noticed a foreboding warning in the console when I build and run FastScripts, my macOS scripting utility:

FastScripts [...] is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!

Ruh-roh, that doesn’t sound good. Particularly with the emphasis of three, count them three, exclamation points! I better figure out what’s going on here. But how?

Sometimes when Apple adds a log message like this, they are kind enough to offer advice about what to do to alleviate the problem. Sometimes the advice implores that we stop using a deprecated method, or in a scenario like this, offers a symbolic breakpoint we might set to zero in on exactly where the offending code lies. For example, a quick survey of my open Console app reveals:

default	11:54:18.482226 -0400	com.apple.WebKit.WebContent	Set a breakpoint at SLSLogBreak to catch errors/faults as they are logged.

This particular warning doesn’t seem to apply to my app, but if it did, I would have something good to go on if I wanted to learn more. With the TIS/TSM warning, however, I have no idea where to go. Do I even use TIS/TSM? What is TSM?

I’ve worked on Apple platforms for long enough to know that TSM stands for Text Services Manager. However, I have also worked on these platforms long enough to forget whether I’ve actually used, or am still using, such a framework in my apps! When these kinds of warnings appear in the console, as many times as not they reflect imperfections in Apple’s own framework code. Is it something Apple’s doing, or something I’m doing, that’s triggering this message?

Ideally we could set a breakpoint on the very line of code that causes this console message to be printed. This can be surprisingly difficult though. There have always been a variety of logging mechanisms. Should you set the breakpoint on NSLog, os_log, printf, fprintf, or write? I could probably figure out a comprehensive method for catching anything that might write to the console, but am I even sure this console method is being generated in my app’s main process? There are a lot of variables here. (Hah! In this particular case, I ended up digging deeper and discovering it calls “CFLog”).

This is a scenario where combining lldb’s powerful “regular expression breakpoints” and “breakpoint commands” can help a great deal. Early in my app’s launch, before the warning messages are logged, I break in lldb and add a breakpoint:

(lldb) break set -r TIS|TSM.*

I’m banking on the likelihood that whatever function is leading to this warning contains the pertinent framework prefixes. It turns out to be a good bet:

Breakpoint 5: 634 locations.

I hit continue and let my app continue launching. Here’s the problem, though: those 634 breakpoint locations include quite a few that are getting hit on a regular basis, and for several consecutive breaks, none of them is triggering the warning message I’m concerned about. This is a situation where I prefer to “let it rip” and sort out the details later:

(lldb) break command add 5
Enter your debugger command(s).  Type 'DONE' to end.
> bt
> c
> DONE
(lldb) c
Process 16022 resuming

What this does is add a series of commands that will be run automatically by lldb whenever breakpoint 5 (the one I just set) is hit. This applies to any of the 634 locations that are associated with the regular expression I provided. When the breakpoint is hit, it will first invoke the “bt” command to print a backtrace of all the calls leading up to this call, and then it will invoke the “continue” command to keep running the app. After the app has run for a bit, I search the debugger console for “!!!” which I remembered from the original warning. Locating it, I simply scroll up to see the backtrace command that had most recently been invoked:

  thread #7, stop reason = breakpoint 5.568
    frame #0: 0x00007fff2cd520fd HIToolbox`TSMGetInputSourceProperty
    frame #1: 0x00000001003faef2 RSFoundation`-[RSKeyboardStatus update](self=0x00006000002b8c00, _cmd="update") at RSKeyboardStatus.m:56
    [...]

Command #2 'c' continued the target.
2018-08-14 12:15:40.326538-0400 FastScripts[16022:624168] pid(16022)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!! 

Sure enough, that’s my code. I’m calling TSM framework functions to handle key translation for FastScripts’s keyboard shortcut functionality, and I’m doing it (gasp!) from thread #7, which is certainly not the main thread. I oughta be ashamed…

But I’m proud, because I tracked down the root of the problem pretty efficiently using lldb’s fantastic breakpoint commands. Next time you’re at a loss for how or where something could possibly be happening, consider the possibility of setting a broad, regular expression based breakpoint, and a series of commands to help clarify what’s happening when those breakpoints are hit. Then? Let it rip!