I'd like to make an action like the "Trigger macro by name" but instead of triggering the macro, just toggling the enable for it.
Is there a simple way to do this?
I'd like to make an action like the "Trigger macro by name" but instead of triggering the macro, just toggling the enable for it.
Is there a simple way to do this?
Use the Set Macro or Group Enable
action.
Use it twice (one set to βEnableβ, the other set to βDisableβ) if you want toggle programmatically, for example, in a switch
action.
If you mean "so I can search for and select a macro or Group at runtime and enable/disable it" then, AFAIK, there's no simple way.
The not-so-simple way would be to use AppleScript (or JXA) to get macros with asstring
, parse the resulting plist to extract the UUID:name pairs for all Groups and macros, and convert that into a one-per-line list of the format UUID__macroName
, eg
F9A89CF4-D9C2-4DCD-9F48-B81D6BE5FB78__My Test Macro
C8C55A5C-52FD-4E50-9629-E44BF3C22D95__My Other Macro
You'd then present that list with a "Prompt with List" action, and use the returned UUID in an "Execute an AppleScript" action:
set inst to system attribute "KMINSTANCE"
tell application "Keyboard Maestro Engine"
set theUUID to getvariable "Local_theUUID" instance inst
do script "<dict>
<key>Action</key>
<string>Toggle</string>
<key>MacroActionType</key>
<string>SetMacroEnable</string>
<key>MacroUID</key>
<string>" & theUUID & "</string>
</dict>"
end tell
...to toggle the state of the chosen macro/Group.
The tricky bit is parsing the plist. It would be better to do it "properly", but I'm not smart enough for that and would instead rely on what I believe is consistent text formatting -- 4 tab
s then <key>name<
and the next line contains a macro name and the next <key>uid<
will be followed by that macro's UUID. Similarly with Macro Groups, but with only 2 tab
s.
Edit to add:
If I'm wrong, and there is a simple way -- I'm going to feel a complete prat. But that's never stopped me before and it won't stop me now, so here's a POC of the above:
Runtime Enablement Toggle.kmmacros (5.8 KB)
I say "POC" because "For Each"ing through a plist dump in this way is relatively slow -- proper parsing (by someone who knows what they're doing!) would speed this up a lot, even using JXA and doing the text manipulation as an adjunct to getting the plist would help (AS's lack of regex is a killer in these situations).
But doing it in KM does at least show clearly what's happening...
Super cool! It takes around 8 seconds to run in my environment, but I wonder if that could be sped up.
If not, is this a feature you would consider adding? @peternlewis
What about this AppleScript ?
set kmInst to system attribute "KMINSTANCE"
tell application "Keyboard Maestro Engine"
set theMacro to getvariable "Local__Macro" instance kmInst
end tell
tell application "Keyboard Maestro"
set enabled of macro theMacro to false
end tell
I've been using this for over 5 years. It uses the same components as the @devoy and @Nige_S suggestions.
Enable-Disable Macro.kmmacros (4.6 KB)
Hope it helps,
KC
It will, however, launch the KM Editor if it isn't already running. Not a problem for me since the Editor is always open, but might be for whatever OP's doing.
Both methods also have an issue with duplicate names -- yours will, I believe, always target the first found macro/Group with a matching name (search order is, I think, alphabetically by Group and then within the Group) while mine will target the right macro but with no context provided in the pick list when choosing...
Ooh..great point. My editor is also always running, and duplicate macro names are not a problem for me. But a great reminder of our unconscious blinders.
Okay! Easy fix. Change "..the macro name.." to "..the macro id UUID.."
That takes care of duplicate macro names. Still needs to have the editor open, I'm afraid.
A variant:
Prompt as List
action[x]
Enabled[ ]
DisabledToggle ENABLED state of macros by name.kmmacros (8.5 KB)
Name-sorted listing of macros with uuid and enabled status:
(() => {
"use strict";
const
onText = "[x]",
offText = "[ ]";
const main = () => {
const macros = Application("Keyboard Maestro").macros;
return sortOn(x => x.name)(
zipWith3(
id => name => enabled => ({ id, name, enabled })
)(
macros.id()
)(
macros.name()
)(
macros.enabled()
)
)
.map(x => `${x.id}__${stateChar(x.enabled)}\t${x.name}`)
.join("\n")
};
const stateChar = enabled =>
enabled
? onText
: offText;
// --------------------- GENERIC ---------------------
// comparing :: Ord a => (b -> a) -> b -> b -> Ordering
const comparing = f =>
// The ordering of f(x) and f(y) as a value
// drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
x => y => {
const
a = f(x),
b = f(y);
return a < b
? -1
: a > b
? 1
: 0;
};
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = f =>
// A copy of xs sorted by the comparator function f.
xs => [...xs].sort(
(a, b) => f(a)(b)
);
// sortOn :: Ord b => (a -> b) -> [a] -> [a]
const sortOn = f =>
// Equivalent to sortBy(comparing(f)), but with f(x)
// evaluated only once for each x in xs.
// ('Schwartzian' decorate-sort-undecorate).
xs => sortBy(
comparing(x => x[0])
)(
xs.map(x => [f(x), x])
)
.map(x => x[1]);
// zipWith3 :: (a -> b -> c -> d) ->
// [a] -> [b] -> [c] -> [d]
const zipWith3 = f =>
xs => ys => zs => Array.from({
length: Math.min(
...[xs, ys, zs].map(x => x.length)
)
}, (_, i) => f(xs[i])(ys[i])(zs[i]));
// sj :: a -> String
const sj = (...args) =>
// Abbreviation of showJSON for quick testing.
// Default indent size is two, which can be
// overriden by any integer supplied as the
// first argument of more than one.
JSON.stringify.apply(
null,
1 < args.length && !isNaN(args[0])
? [args[1], null, args[0]]
: [args[0], null, 2]
);
return main();
})();
Enabled status updating, and listing of results:
return (() => {
"use strict";
const
onText = "[x]",
offText = "[ ]";
const main = () => {
const
allMacros = Application("Keyboard Maestro").macros,
chosenMacros = kmvar
.local_Choices
.trim()
.split("\n")
.map(x => allMacros.byId(x));
return either(
() => ""
)(
report => report
)(
fmapLR(newState => {
const prefix = newState
? onText
: offText;
return chosenMacros
.map(
x => (
// EFFECT,
x.enabled = newState,
// VALUE.
`${prefix} ${x.name()}`
)
)
.join("\n");
})(
0 < chosenMacros.length
? Right(
!chosenMacros[0].enabled()
)
: Left("No macros chosen.")
)
);
};
// --------------------- GENERIC ---------------------
// Left :: a -> Either a b
const Left = x => ({
type: "Either",
Left: x
});
// Right :: b -> Either a b
const Right = x => ({
type: "Either",
Right: x
});
// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
// Application of the function fl to the
// contents of any Left value in e, or
// the application of fr to its Right value.
fr => e => "Left" in e
? fl(e.Left)
: fr(e.Right);
// fmapLR (<$>) :: (b -> c) -> Either a b -> Either a c
const fmapLR = f =>
// Either f mapped into the contents of any Right
// value in e, or e unchanged if is a Left value.
e => "Left" in e
? e
: Right(f(e.Right));
// MAIN ---
return main();
})();
I'm coming to this topic very late. (Real life intrudes.)
In an app with tons of friendly and thoughtful features (and a forum to match!), the getmacros command for an in-memory plist is one of KM's more forbidding. Moreover, it also has a bit of a lopsided effort-reward balance. Here, the advantage of not launching the Editor just to toggle groups and macros means frogging with an in-memory plist via Apple's Key-Value Coding (KVC) query conventions--an acquired taste in AppleScript, a spectacle of incantatory pomp and circumstance in AppleScriptObjC (nonetheless, the toolset geared for plist wrangling).
There is, however, a minor related feature available in getmacros:
the macro group activation toggle uid. (Also present in the xml of the macro group.)
(Thanks to @Nige_S for showing me the way on this.)
As I tried to remember how to query this in-memory plist, a prior observation by the ever-sharp eyed and even sharper-witted @Nige_S came to mind--finally--who noticed that some macros in gethotkeys are not macros, but rather macro group hot key entries. That is, for any "macro" entry with a name value suffixed with [Macro Group] and sort value prefixed with ξ Ίξ Ί): macro entry β macro. Of course, I foolishly ignored this until now.
It turns out that for [Macro Group] macro entries in both gethotkeys and in getmacros: 1. uid β macro id 2. uid β group id. Rather, uid is the value of the ToggleMacroUID key in the xml of the macro group in Editor.
Pass this uid as a parameter to the "do script" Engine AppleScript and, voilΓ , it will toggle the active state of any macro group that is set to other that "Always Available".
In the dynamic gethotkeys listings, the [Macro Group] entry vanishes when the macro group--its hot keys--becomes unavailable.
The entry is a bit less dynamic in getmacros. It is present even when the macro group is unavailable, but vanishes when the macro goup is disabled.
Since the "uid" (ie.,ToggleMacroUID) won't be valid when the macro group is disabled, this makes sense.
The downside is that getmacros can't be used for getting a comprehensive listing of macro groups with hot keys and their symbols...but that is another discussion.
Smart Group Syntax for Keyboard Shortcuts: how to include Macro Group hot keys - Questions & Suggestions - Keyboard Maestro Discourse
Here is a macro that toggles the enabled state of macro groups and macros or the activation state of macro groups that are enabled and the setting is other that "Always Activated".
Toggle enable or activate from Prompt List of macros or groups.kmmacros (12.7 KB)
USAGE:
Enable the macro and run.
Select a name of macro or group from the Prompt with List.
A macro group or macro selection toggles its enabled state.
A toggle group activation selection toggles the activation state of the group. Palettes will show or hide.
Entries are prefixed with:
(I didn't try to remove the trailing [Macro Group] from toggle activation group entries.)
A trailing (x) shows that the group or macro is disabled.
A notification will display either:
followed by (G) or (M) and then the name of the group or macro.
Generates prompt list source (uid__group|macro name):
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
property author : "@CRLF"
--ββββββββββββββββββββββββ
--β PURPPOSE:
--β Output "UID__macro|group name"
--β prompt for list lines
--ββββββββββββββββββββββββ
tell application id "com.stairways.keyboardmaestro.engine" to set groups to current application's NSPropertyListSerialization's propertyListWithData:((current application's NSArray's arrayWithObject:(getmacros))'s firstObject()'s |data|()) options:0 format:(missing value) |error|:(missing value)
set macros to (groups's valueForKeyPath:"@unionOfArrays.macros")
set m to macros's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"NOT (%K BEGINSWITH %@)" argumentArray:{"sort", "ξ Ίξ Ί)"})
set gtog to macros's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"%K BEGINSWITH %@" argumentArray:{"sort", "ξ Ίξ Ί)"})
set {genabled, gnames, guids} to (groups's dictionaryWithValuesForKeys:{"enabled", "name", "uid"})'s objectsForKeys:{"enabled", "name", "uid"} notFoundMarker:"not found"
set {menabled, mnames, muids} to (m's dictionaryWithValuesForKeys:{"enabled", "name", "uid"})'s objectsForKeys:{"enabled", "name", "uid"} notFoundMarker:"not found"
set {gtenabled, gtnames, gtuids} to (gtog's dictionaryWithValuesForKeys:{"enabled", "name", "uid"})'s objectsForKeys:{"enabled", "name", "uid"} notFoundMarker:"not found"
set promptArray to current application's NSMutableArray's array()
set disabledSymbol to " (x)"
set i to 0
repeat with aUID in guids
set enabledSymbol to ""
if not (genabled's objectAtIndex:i) then set enabledSymbol to disabledSymbol
set theLine to (current application's NSString's stringWithFormat_("%@__%@ %@ %@", aUID, "(G)", (gnames's objectAtIndex:i), enabledSymbol))
(promptArray's addObject:theLine)
set i to i + 1
end repeat
set i to 0
repeat with aUID in muids
set enabledSymbol to ""
if not (menabled's objectAtIndex:i) then set enabledSymbol to disabledSymbol
set theLine to (current application's NSString's stringWithFormat_("%@__%@%@", aUID, (mnames's objectAtIndex:i), enabledSymbol))
(promptArray's addObject:theLine)
set i to i + 1
end repeat
set i to 0
repeat with aUID in gtuids
set enabledSymbol to ""
if not (gtenabled's objectAtIndex:i) then set enabledSymbol to disabledSymbol
set theLine to (current application's NSString's stringWithFormat_("ξ Ί%@__%@ %@ %@", aUID, "(TG)", (gtnames's objectAtIndex:i), enabledSymbol))
(promptArray's addObject:theLine)
set i to i + 1
end repeat
return (promptArray's componentsJoinedByString:linefeed) as text
Toggles enable of macro or group or activation state of group with uid output.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
--ββββββββββββββββββββββββ
--β PURPPOSE:
--β Process a uid to
--β toggle enable of macro or group or
--β activation state of group
--ββββββββββββββββββββββββ
tell application id "com.stairways.keyboardmaestro.engine"
set theUID to getvariable "localUID" instance system attribute "KMINSTANCE"
end tell
if theUID is not "" then
if theUID does not start with "ξ Ί" then
set toggleEnableXML to Β¬
"<dict>
<key>Action</key>
<string>Toggle</string>
<key>MacroActionType</key>
<string>SetMacroEnable</string>
<key>MacroUID</key>
<string>" & theUID & "</string>
</dict>"
tell application id "com.stairways.keyboardmaestro.engine"
do script toggleEnableXML
end tell
else
set theUID to text 2 thru length of theUID
tell application id "com.stairways.keyboardmaestro.engine"
do script theUID
end tell
end if
set theResults to enabledStatusOfMacroOrGroupUID(theUID)
if theResults β missing value then
set {objectType, objectName, enabledStatus} to theResults
if enabledStatus is "true" then set enabledSymbol to "β
"
if enabledStatus is "false" then set enabledSymbol to "β"
if enabledStatus is "toggled" then set enabledSymbol to "π"
if objectType is "group" then set typeSymbol to "(G)"
if objectType is "macro" then set typeSymbol to "(M)"
display notification typeSymbol & space & enabledSymbol & space & objectName
end if
return
end if
on enabledStatusOfMacroOrGroupUID(theUID)
tell application id "com.stairways.keyboardmaestro.engine" to set groups to current application's NSPropertyListSerialization's propertyListWithData:((current application's NSArray's arrayWithObject:(getmacros))'s firstObject()'s |data|()) options:0 format:(missing value) |error|:(missing value)
set macros to (groups's valueForKeyPath:"@unionOfArrays.macros")
set matches to macros's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"%K == %@" argumentArray:{"uid", theUID})
set m to matches's firstObject()
if m β missing value then
if (m's objectForKey:"sort")'s hasPrefix:"ξ Ίξ Ί)" then
set theObject to "group"
set enabledStatus to "toggled"
else
set theObject to "macro"
set enabledStatus to ((m's objectForKey:"enabled") as boolean) as text
end if
return {theObject, (m's objectForKey:"name") as text, enabledStatus}
else
set matches to groups's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"%K == %@" argumentArray:{"uid", theUID})
set g to matches's firstObject()
if g β missing value then
set theObject to "group"
set enabledStatus to ((g's objectForKey:"enabled") as boolean) as text
return {theObject, (g's objectForKey:"name") as text, enabledStatus}
end if
end if
return missing value
end enabledStatusOfMacroOrGroupUID
If anyone wants to read these in-memory plists using KVC, just ask. I'll try to untangle some of the underbrush of the interface the best I can.
Awesome work!