Search code examples
python-3.xmacoscronappkitnsworkspace

AppKit's `NSWorkspace.activeApplication()` returns `None` when run by cron


So I'm trying to keep track of application usage by grabbing the active window at regular intervals. My goal is to have daily/weekly data of what applications were used, and for [roughly] how long.

The active-window-name grabbing happens thanks to [Python3 and]:

from AppKit import NSWorkspace
awn = NSWorkspace.sharedWorkspace().activeApplication()["NSApplicationName"]

This works great when run from terminal, so I wanted to have it run every minute, and looked to cron and was able to get past MacOS' cron filesystem-access limitations (by putting the script and DB in /Users/Shared) - Thanks SO!

Now, NSWorkspace.sharedWorkspace().activeApplication() seems to always return None when the script is run by cron, even though it's run as my user...?

I'm assuming it's something akin to the TCC sandboxing FS acccess that is getting in the way of fetching the correct workspace...?

I'd like to avoid requiring any user intervention (opening terminal, launching a script, etc.) on startup/login and ideally also avoid having a script running all the time, but sleeping 59.6s/minute.


Solution

  • So I tried a few unfruitful things, such as trying to get a reverse shell as cron to directly walk through what's available to NSWorkspace...

    In the end I stumbled across a post on launchctl and .plist files and [with some tinkering] it worked!!

    launchctl jobs seem to be subject to similar TCC(?) sandboxing as cron jobs, so the script must live/play in either /Users/Shared and/or /tmp (still avoiding the ☢️Enable Full Disk Access☢️ option).

    Another quirk encountered was that, for some reason, the Python script starts out running in / (which it doesn't have read/write access to - see above), so using the <key>WorkingDirectory</key> option below was necessary.

    Here is the sample .plist file, [symlinked from] ~/Library/LaunchAgents, including the requisite

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
        "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Label</key>
        <string>app-use_tracker</string>
        <key>ServiceDescription</key>
        <string>Application usage-tracker</string>
        <key>ProgramArguments</key>
        <array>
            <!-- only /tmp and /Users/Shared seem accessible -->
            <string>/Users/Shared/app-use_tracker.py</string>
        </array>
        <key>RunAtLoad</key>
        <false/>
        <key>StartCalendarInterval</key>
        <!-- Missing Minute,Hour,Day,Weekday,Month are considered to be wildcard. -->
        <dict>
            <!-- <key>Minute</Key><integer></integer>
            <key>Hour</Key><integer></integer>
            <key>Day</Key><integer></integer>
            <key>Weekday</Key><integer></integer>
            <key>Month</Key><integer></integer> -->
        </dict>
        <key>StandardOutPath</key>
        <string>/tmp/launch-app.log</string>
        <key>StandardErrorPath</key>
        <string>/tmp/launch-app.err</string>
        <key>WorkingDirectory</key>
        <string>/Users/Shared/</string>
    </dict>
    </plist>
    

    Next TODOs:

    • get the .plist to load automatically at startup/login (putting it in ~/Library/LaunchAgents, alone, may do it)
    • figure out most restrictive permissions that will allow this to run

    EDIT: Done!

    Since .plists placed in ~/Library/LaunchAgents run as the user, read-only for that user is sufficient!

    -r-------- 1 adam wheel 1.6K Nov 4 14:26 /Users/adam/Library/LaunchAgents/app-use_tracker.plist