Working with Keyboard Maestro and Setapp apps

I have been using Keyboard Maestro with Setapp but there are three problems:

  1. the CFBundleIdentifier will almost certainly be different:
    com.bjango.istatmenus
    vs com.bjango.istatmenus-setapp

  2. the path to the app will be different
    /Applications/iStat Menus.app
    vs /Applications/Setapp/iStat Menus.app

  3. the app may be named differently (Setapp does not use version numbers in app names)
    /Applications/Downie 3.app (com.charliemonroe.Downie-3)
    vs /Applications/Setapp/Downie.app (com.charliemonroe.Downie-setapp)

Downie hits the trifecta.

I can't think of any way to deal with this in Keyboard Maestro other than to put conditionals in for either option: i.e. if I want to send an URL to Downie via Keyboard Maestro, I would have to look to see which is installed, and use the first one that I see.

But I thought it might be worth mentioning in case @peternlewis has any secret solutions that I hadn't thought of.

Hey @tjluoma,

Please give one or more specific use-cases where this will be an issue for you.

-Chris

Generally, if the bundle ID is different, the app is different as far as Keyboard Maestro is concerned.

And if it is at a different path, then it is generally considered different as well (although Keyboard Maestro 9 has ways of reducing that).

So basically, no, as far as Keyboard Maestro is concerned, these are different apps and you'll have to deal with them in any macro group or action that refers to them.

I often run betas of OmniFocus, and I've run into similar issues with those over the years. I've never actually gone to the trouble to check bundle IDs, etc.; I just keep updating the relevant macros and palettes each time they stop working.

A JavaScript for Automation function from application name to id of installed version ?

(() => {
    'use strict';

    // installedIDFromAppName :: String -> String
    const installedIDFromAppName = appName => {
        try {
            return Application(appName).id();
        } catch (e) {
            return '"' + appName + '" :: ' + e.message;
        }
    };

    return installedIDFromAppName('TaskPaper');

    //--> e.g. 'com.hogbaysoftware.TaskPaper3.direct'
})();
1 Like

Any time there is an "activate application" action in Keyboard Maestro, it will fail if I set it up on a Mac with iStat Menus in /Applications/ but now iStat Menus is in /Applications/Setapp/.

Any time there is a "hide application" it won't realize that it's the same application.

The former is actually pretty easy to "fake" because I can use open -a "iStat Menus"

I can hide iStat Menus with this:

osascript -e 'tell application "System Events" to set visible of process "iStat Menus" to false'

but I can't tell Keyboard Maestro to take action when iStat Menus activates, hides, or quits without making separate rules for both installation locations.

Hey Rob,

Nice.

Now – can you do it with vanilla AppleScript and/or AppleScriptObjC?

-Chris

Here we get the difference between late-binding and early-binding compilation strategies.

AppleScript, as you know, expects the application name to be resolved before run-time.

I seem to remember that there is a route through the Foundation or AppKit classes – I'll have a look.

UPDATE

If the app is running then:

https://developer.apple.com/documentation/appkit/nsworkspace/1534059-runningapplications?language=objc

and

https://developer.apple.com/documentation/appkit/nsworkspace?language=objc

Tho if the app is running we could in any case write sth like:

on run
    bundleIDFromAppName("TaskPaper")

   --> e.g. "com.hogbaysoftware.TaskPaper3.direct"
end run


-- bundleIDFromAppName :: String -> String
on bundleIDFromAppName(appName)
    tell application "System Events"
        set ps to application processes where name = appName
        if {} ≠ ps then
            bundle identifier of item 1 of ps
        else
            appName & " is not running"
        end if
    end tell
end bundleIDFromAppName

Otherwise, if the app is not running, but we know the path of the installation which interests us:

use framework "Foundation"

on run
    bundleIDFromAppPath("/Applications/TaskPaper.app")
    
    --> e.g. "com.hogbaysoftware.TaskPaper3.direct"
end run


-- bundleIdFromAppPath :: FilePath -> BundleIdentifier String
on bundleIDFromAppPath(fp)
    tell current application to tell its NSBundle
        (bundleIdentifier() of bundleWithPath_(fp)) as string
    end tell
end bundleIDFromAppPath

Hey Rob,

There's no problem at all if the app is running, and you know its name. System Events is quick to return information for such.

The trouble comes when the app is not running, and we don't offhand know the path.

If you know the Bundle-ID then this method is very simple and quick – nor does it launch the app.

tell application "Finder"
   set theApp to application file id "com.latenightsw.ScriptDebugger7" as alias
   set appName to name of theApp
end tell

But what if you only know the app name?

The only way I currently know of is to use Spotlight to find the app.

mdls -raw -name kMDItemCFBundleIdentifier "$(mdfind -onlyin /Applications/ 'kMDItemFSName == "Script Debugger.app"c' | head -n 1)"

And since I'm in the shell anyway I might as well use Spotlight to return the desired attribute.

I would like to find a more robust solution.

-Chris