The most robust solution is to make a system call that checks which modifier keys are currently pressed. The following solution uses small external executable written in C that you can compile yourself from the command line (provided you have Xcode installed).
If you don't know C, don't worry! You'll only need to copy and paste the C code. It's called using a simple AppleScript handler.
Technical Note: The following code is based on macOS's Carbon framework. It should also be possible to create a Cocoa solution that would allow everything to be called directly from AppleScript using AppleScript-Objective-C.
Create the Compiled Executable
Follow these instructions to create a command-line program that prints the current modifier keys. You must have the Xcode Command Line Tools (or the full Xcode) installed in order to compile the code.
Save the following code as getModifierKeys.c
in the Desktop folder:
#include <Carbon/Carbon.h>
// Define the key code for the fn Key.
const unsigned int fnKey = 131072;
// Define the accepted labels for the modifier keys. The first label is used by the program for output.
const char *cmdLabels[] = {"command", "cmd", "@", "⌘"};
const char *controlLabels[] = {"control", "ctrl", "ctl", "^", "⌃"};
const char *optionLabels[] = {"option", "opt", "alt", "~", "⌥"};
const char *shiftLabels[] = {"shift", "$", "⇧"};
const char *fnLabels[] = {"fn", "function", "func"};
const char *alphaLabels[] = {"caps", "caps lock", "caps_lock", "caps-lock", "capslock", "alpha", "⇪"};
// Define the order to use when returning the modifier keys.
const unsigned int modifierKeyValues[] = {cmdKey, controlKey, optionKey, shiftKey, fnKey, alphaLock};
const char **modifierKeyLabels[] = {cmdLabels, controlLabels, optionLabels, shiftLabels, fnLabels, alphaLabels};
const unsigned int modifierKeyLabelsCount[] = {(sizeof cmdLabels / sizeof *cmdLabels), (sizeof controlLabels / sizeof *controlLabels), (sizeof optionLabels / sizeof *optionLabels), (sizeof shiftLabels / sizeof *shiftLabels), (sizeof fnLabels / sizeof *fnLabels), (sizeof alphaLabels / sizeof *alphaLabels)};
// Get the length of the above arrays.
const unsigned int modifierKeyCount = (sizeof modifierKeyValues / sizeof *modifierKeyValues);
// Define the label to use for no modifier keys.
const char *noneLabel = "none";
// Declare the helper function to determine matches to the labels.
int arg_match(const char *, const char *[], int);
// Main function.
int main (int argc, const char *argv[]) {
// Get the current modifier key codes.
unsigned int current_modifier_keys = GetCurrentKeyModifiers();
unsigned int i;
unsigned int modifiers_count = 0;
for (i = 0; i < modifierKeyCount; i++) { // Loop through all possible modifier keys again, and print the label.
if (current_modifier_keys & modifierKeyValues[i]) {
if (modifiers_count) { printf(" "); }
modifiers_count++;
printf("%s", modifierKeyLabels[i][0]);
}
}
if (modifiers_count) {
printf("\n");
} else {
printf("%s\n", noneLabel);
}
return 0;
}
// Helper function to determine matches to the labels.
int arg_match(const char *arg_string, const char *key_labels[], int key_labels_length) {
for (int i = 0; i < key_labels_length; i++) {
if (0 == strcasecmp(arg_string, key_labels[i])) {
return 1;
}
}
return 0;
}
Compile the program by running the following commands from Terminal:
cd ~/Desktop
gcc -framework Carbon getModifierKeys.c -o getModifierKeys
You will now have an executable file named getModifierKeys
on your Desktop. Move this to wherever you want to store it.
Call the Executable from AppleScript
The following handler is used to call the executable for AppleScript. The handler will block until the specified modifier keys are no longer pressed.
Copy the following handler into your AppleScript, and replace /PATH/TO/EXECUTABLE
with the absolute path to the executable you created in the previous step (if you didn't move it, it will be located at /Users/USERNAME/Desktop/getModifierKeys
).
to waitForModifierKeyRelease(modifier_keys)
(* (string OR list of strings) → nothing
Block execution until all of the keys specified in modifier_keys are released.
Modifier keys are specified by their name; multiple keys may be specified in a space-delimited string or in a list.
The possible modifier keys are "command", "control", "option", "shift", "fn", and "caps".
Parameters:
modifier_keys [string OR list of strings] : The modifier keys to await release.
Result:
[nothing] : No return value. *)
set should_wait to true
repeat while should_wait
set current_modifier_keys to do shell script quoted form of "/PATH/TO/EXECUTABLE"
set should_wait to false
set should_wait to should_wait or (current_modifier_keys contains "command" and modifier_keys contains "command")
set should_wait to should_wait or (current_modifier_keys contains "control" and modifier_keys contains "control")
set should_wait to should_wait or (current_modifier_keys contains "option" and modifier_keys contains "option")
set should_wait to should_wait or (current_modifier_keys contains "shift" and modifier_keys contains "shift")
set should_wait to should_wait or (current_modifier_keys contains "fn" and modifier_keys contains "fn")
set should_wait to should_wait or (current_modifier_keys contains "caps" and modifier_keys contains "caps")
end repeat
return
end waitForModifierKeyRelease
Call the handler before the keystroke
command (or any other commands that behave unpredictably when modifier keys are pressed), specifying the keys that you don't want to be pressed.
my waitForModifierKeyRelease({"command", "control", "option", "shift"})
The "fn" and "caps" modifiers are not used in this example; note also that the "caps" modifier indicates whether caps lock is active, not whether the key is physically pressed.