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?
(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)
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.
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.
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?
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.
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...
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.
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!
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
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?
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
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
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ÂĄ&<me>"'
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:"'")'s stringByReplacingOccurrencesOfString:"\"" withString:""") as text
-->testÂĄ&<me>"'
return encodedText
--> testÂĄ&<me>"'