How to programmatically update an action in another macro?

Macro #1 dynamically sets a variable ScriptPath to a certain path. Macro #2 has an action that hardcodes that path by setting the variable to a literal string of text.

How do I programmatically update the text contents of the action in macro #2 from macro #1?

Global variable ?


You have an example ?

(There are also workflows involving a flow like Action → XML → JSON → updated JSON → updated XML → updated Action but it doesn't sound as if you should need that, and KM's XML serialisation is not guaranteed to remain unchanged)

1 Like

So you want Mac#2 to update a variable used by Mac#1, but you want Mac#1 to be able to change that value? That’s odd. Global variables are probably the solution. But then you may want to add an IF statement that says “If the value of this global variable is empty, then set it to a specific string value.” That will deal with the special case of the value not being initialized. I do this sort of thing often, both with numeric and string values.

1 Like

The use case is as follows: Macro #1 is a “setup” macro that prompts the user to select a folder and sets a global variable `ScriptPath` to the tilde-abbreviated path path of that folder.

Macro #2 runs whenever the KM engine is launched. It sets the global variable `ScriptPath` to a hard-coded path. This way, the path will be synced across devices, and if the user later deletes global variables, the path is reset whenever the engine launches.

Macro #2 contains other actions, but when macro #1 is run, I want it to change the text included in the “set variable action” in macro #2.

I’m confused here. What devices are you talking about? KM runs on only one device: a Mac.

I mean when you sync macros across multiple Mac devices. (Global variables are not synced.)

Ok, in that case I can’t help, because I don’t know how to sync. That’s a new requirement you didn’t mention in your original post. In any case, self-modifying code, in the way you want to do it, is possible (at least on a single Mac) but is highly discouraged because it’s undocumented and may break in future versions of KM. Do you really want to use undocumented features?

Correct but you can still copy them from ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Variables.sqlite if you need them.

1 Like

That’s true, but a file system can be shared among two computers, so if you store the value of the string in a simple text file, then both devices can read (or write to) the content of that file to get (or set) the value. I think this is your best solution: to sync via a shared file.

2 Likes

KM Editor AppleScript provides for updating the XML of of an action, but it can be a bit of a rigamarole that requires the Editor to be open.

Macro #1 would need to know the macro id of Macro #2.

And it's easiest if the targeted action is at the top level.

If there are more than one of the same type of action at the top level of Macro#2, there needs to. be some way of identifying which needs the update. Something like setting a note in the action to something like: "path wants update". Or positioning it at the top of the macro.

EDIT: Or naming the action to something like "My ScriptPath Setter"

Is this really necessary?

I am kind of horrible about reading intent.

Where is Macro #1 in relation to Is Macro #2?

Presumably A hard-coded path means THE hard-coded path?

Another thought:

If you're trying to guard against user messing with the variable, I imagine there is a way to write/read from user defaults in MacOs.

@ComplexPoint might have this more readily available than I, but if I rummage around, I could likely find something that might work...

2 Likes

In your distinction between tilde-abbreviated and "hard-coded",

does hard-coded mean

  1. expanded (folder selection tilde-expanded to a specific user path), or
  2. some constant literal supplied from elsewhere ?
1 Like

As @Airy mentioned, how about writing and reading the path to a cloud file in a folder that is globally available?

Give that some serious consideration because the alternative is complicated and somewhat fragile. But if you must…

With the editor open and in the foreground, Macro #1 could delete the original action that contains the path and, using commands documented in the Keyboard Maestro Scripting dictionary, recreate it with the updated path (from XML that would be dynamically built specifying the new path).

I do something similar to create a boilerplate header comment for some of my macros. If you are interested, I’d be glad to share it.

1 Like

Since it seems like it's quite a complex process to programmatically edit the action, I suppose I'll just use a simpler method like some have suggested. I appreciate everyone’s input!

1 Like

Undoubtedly the wiser choice.

The Editor AppleScript,however, does allow for it.

(Note: the targeted action cannot be nested. There is a way.This a basic version)

So just for fun:

update an action in another macro.kmmacros (8.2 KB)

Macro Images

AppleScript
 use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set kminstance to system attribute "KMINSTANCE"

tell application id "com.stairways.keyboardmaestro.engine"
	set macroActionTargetType to getvariable "localMacroActionTargetType" instance kminstance
	set newPath to getvariable "localNewPath" instance kminstance
	set macro2ID to getvariable "localTargetMacroID" instance kminstance
	set actionName to getvariable "localTargetActionName" instance kminstance
end tell

--TO DEBUG/TEST SET CONTEXT VARIABLES HERE:
if name of current application is not "osascript" then
	set macroActionTargetType to "SetVariableToText"
	set newPath to "/an updated path"
	set macro2ID to "1350EB6A-CBE6-4679-A883-D7AEB27857B9"
	set actionName to "ScriptPath Setter"
end if
set targetAction to missing value
tell application id "com.stairways.keyboardmaestro.editor"
	try
		set targetAction to (action actionName of macro id macro2ID)
		set actionOldXML to xml of targetAction
	on error msg
		tell application id "com.stairways.keyboardmaestro.engine"
			do script my displayInKMWindow(msg)
			return
		end tell
	end try
end tell

set oldActionDict to (current application's NSString's stringWithString:actionOldXML)'s propertyList()
set isTargetMacroActionType to (oldActionDict's objectForKey:"MacroActionType") as text is macroActionTargetType
if isTargetMacroActionType then
	oldActionDict's setObject:newPath forKey:"Text"
	set updatedActionXML to serializePlistObject(oldActionDict)
else
	tell application id "com.stairways.keyboardmaestro.engine"
		do script my displayInKMWindow("Action named:" & actionName & "is not macro action type:" & macroActionTargetType)
		return
	end tell
end if

tell application id "com.stairways.keyboardmaestro.editor"
	set xml of targetAction to updatedActionXML
end tell
on serializePlistObject(plistObject)
	set {theData, theError} to current application's NSPropertyListSerialization's dataWithPropertyList:plistObject format:(current application's NSPropertyListXMLFormat_v1_0) options:0 |error|:(reference) -- don't use binary format
	if theData is missing value then error (theError's localizedDescription() as text) number -10000
	set plist to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSUTF8StringEncoding)) as text
end serializePlistObject

on displayInKMWindow(theText)
	"<array>
        <dict>
            <key>Action</key>
            <string>DisplayWindow</string>
            <key>MacroActionType</key>
            <string>InsertText</string>
            <key>StyledText</key>
            <data></data>
            <key>Text</key>
            <string><![CDATA[" & theText & "]]></string>
        </dict>
</array>"
end displayInKMWindow

1 Like

See:

for an example of creating a macro.

You can edit actions in a macro by assigning the xml using AppleScript. So it would not be very difficult to do what you want to do.

2 Likes

I see. So if I have this action XML, how would I use AppleScript to add it as the first (or last) action to the macro called “Set Keyboard Maestro ENV Variables”, which is in the same macro group as the executing macro?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>ActionName</key>
		<string>Paste Your Script Path in the Action Below</string>
		<key>ActionUID</key>
		<integer>100075176</integer>
		<key>MacroActionType</key>
		<string>SetVariableToText</string>
		<key>Text</key>
		<string>%Variable%PythonScriptsPath%</string>
		<key>Variable</key>
		<string>PythonScriptsPath</string>
	</dict>
</array>
</plist>

Here is one way:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

set theXML to "<dict>
		<key>ActionName</key>
		<string>Paste Your Script Path in the Action Below</string>
		<key>ActionUID</key>
		<integer>100075176</integer>
		<key>MacroActionType</key>
		<string>SetVariableToText</string>
		<key>Text</key>
		<string>%Variable%PythonScriptsPath%</string>
		<key>Variable</key>
		<string>PythonScriptsPath</string>
	</dict>"

tell application id "com.stairways.keyboardmaestro.editor"
	tell macro "Set Keyboard Maestro ENV Variables"
	make new action with properties {xml:theXML} at beginning
	end tell
end tell

this will add it at the end

	make new action with properties {xml:theXML} at end

More examples and discussion:

How Do I Insert an Action as the First Action in 206 Macros Automatically - Questions & Suggestions - Keyboard Maestro Discourse

If you have other requirements, I'm happy to with your case, if I can.

3 Likes

Works great, thank you! I ended up making this slight variation:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "Keyboard Maestro Engine"
	set theXML to getvariable "LocaPathActionXML"
end tell


tell application id "com.stairways.keyboardmaestro.editor"
	tell macro "2: Set Keyboard Maestro ENV Variables"
		make new action with properties {xml:theXML} at beginning
	end tell
end tell
1 Like

Great!

From the link Peter provided.

His example that encodes ASCII text for XML using the Filter action with Encode HTML Entities is here:

Macro to Create a Text Expansion Macro from the Selected Text - Macro Library - Keyboard Maestro Discourse

FWIW I use this instead in a lot of my scripts that work with KM xml:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"

set unencodedText to "testÂĄ&<me>\"'"

set encodedText to (current application's NSXMLNode's textWithStringValue:unencodedText)'s XMLString() as text
return encodedText
-->testÂĄ&amp;&lt;me&gt;"'

It doesn't encode single and double quotes but I've not had a problem with that.

This version would do that:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"

set encodedText to (((current application's NSXMLNode's textWithStringValue:unencodedText)'s XMLString()'s stringByReplacingOccurrencesOfString:"'" withString:"&#x27;")'s stringByReplacingOccurrencesOfString:"\"" withString:"&quot;") as text
-->testÂĄ&amp;&lt;me&gt;&quot;&#x27;
return encodedText
--> testÂĄ&amp;&lt;me&gt;&quot;&#x27;
2 Likes