Checking modifier keys

modifiers

(Shane Stanley) #1

This came up elsewhere, but it might be of more general interest. These are a couple of handlers for checking what modifier keys are pressed. The first is for checking if a particular key is pressed, and the second is for getting a list of pressed keys. (You can check for others, but I’ve limited it here to option, shift, control and command.)

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit" -- for NSEvent

-- classes, constants, and enums used
property NSControlKeyMask : a reference to 262144
property NSAlternateKeyMask : a reference to 524288
property NSShiftKeyMask : a reference to 131072
property NSCommandKeyMask : a reference to 1048576
property NSEvent : a reference to current application's NSEvent

-- test script
delay 2 -- give yourself time to press keys
set x to my checkModifier:"option"
set y to modifiersDown()
return {x, y}

on checkModifier:keyName
	if keyName = "option" then
		set theMask to NSAlternateKeyMask
	else if keyName = "control" then
		set theMask to NSControlKeyMask
	else if keyName = "command" then
		set theMask to NSCommandKeyMask
	else if keyName = "shift" then
		set theMask to NSShiftKeyMask
	else
		return false
	end if
	set theFlag to NSEvent's modifierFlags() as integer
	if ((theFlag div theMask) mod 2) = 0 then
		return false
	else
		return true
	end if
end checkModifier:

on modifiersDown()
	set theKeys to {}
	set theFlag to NSEvent's modifierFlags() as integer
	if ((theFlag div NSAlternateKeyMask) mod 2) is not 0 then
		set end of theKeys to "option"
	end if
	if ((theFlag div NSControlKeyMask) mod 2) is not 0 then
		set end of theKeys to "control"
	end if
	if ((theFlag div NSCommandKeyMask) mod 2) is not 0 then
		set end of theKeys to "command"
	end if
	if ((theFlag div NSShiftKeyMask) mod 2) is not 0 then
		set end of theKeys to "shift"
	end if
	return theKeys
end modifiersDown

#2

Shane,

I used your example to do what I was trying to do and it worked. The actual script is at the end of the post. I expanded it a bit to cover more keys.

Your way is a lot more compact and straightforward then my way but for the most the actual the overall idea was similar to mine. The one part that was totally different was creating properties and then setting those properties to references to numbers.

I don’t understand why that made the script so stable and kept it from crashing. It seemed like an odd thing to do but it worked really well. What is so special about using properties set to references to number?

use framework "Foundation"
use framework "AppKit" -- for NSEvent

property NSCapsLockMask : a reference to 65536
property NSShiftKeyMask : a reference to 131072
property NSControlKeyMask : a reference to 262144
property NSOptionKeyMask : a reference to 524288
property NSCommandKeyMask : a reference to 1048576
property NSFunctionKeyMask : a reference to 8388608
property NSEvent : a reference to current application's NSEvent

on KeyPressed(KeyName)
	if KeyName = "caps lock" then
		set TheMask to NSCapsLockMask
	else if KeyName = "shift" then
		set TheMask to NSShiftKeyMask
	else if KeyName = "control" then
		set TheMask to NSControlKeyMask
	else if KeyName = "option" then
		set TheMask to NSOptionKeyMask
	else if KeyName = "command" then
		set TheMask to NSCommandKeyMask
	else if KeyName = "function" then
		set TheMask to NSFunctionKeyMask
	else
		return false
	end if
	
	set MFlags to NSEvent's modifierFlags() as integer
	if ((MFlags div TheMask) mod 2) = 0 then
		return false
	else
		return true
	end if
end KeyPressed

on TheModifiersDown()
	set TheKeys to {}
	set KeyFlags to NSEvent's modifierFlags() as integer
	
	if ((KeyFlags div NSCapsLockMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"caps lock"}
	end if
	if ((KeyFlags div NSShiftKeyMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"shift"}
	end if
	if ((KeyFlags div NSControlKeyMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"control"}
	end if
	if ((KeyFlags div NSOptionKeyMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"option"}
	end if
	if ((KeyFlags div NSCommandKeyMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"command"}
	end if
	if ((KeyFlags div NSFunctionKeyMask) mod 2) is not 0 then
		set TheKeys to TheKeys & {"function"}
	end if
	
	return TheKeys
end TheModifiersDown

-- The caps lock key was on
KeyPressed("caps lock") --> true

delay 2
-- I pressed down the option key
KeyPressed("option") --> true

-- No key is pressed or on
KeyPressed("option") --> false

delay 2
-- The "caps lock" was on and the shift, control, option, command and function keys were pressed down
TheModifiersDown() --> {"caps lock", "shift", "control", "option", "command", "function"}

-- No key is pressed or on
TheModifiersDown() --> {}

(Shane Stanley) #3

It didn’t. What made the difference is that it doesn’t use any script objects.

That’s done by Script Debugger’s code completion with Use Properties for Cocoa Terms on. I could have just set them to the numbers directly, but the a reference to gives SD a clue when I want to use Copy as Standalone Code.

The main reason for using numbers at all is because the actual terms can change. For example, NSShiftKeyMask is now deprecated in favor of NSEventModifierFlagShift — both refer to the same value. But if you happen to use the latter without a property, your script will fail when run under older versions of the OS, because it will fail to be resolved at run time. This change of names has happened with a lot enums as part of the introduction of Swift, where stricter naming rules help integration. By putting the values in properties, you make your script immune to the problem. It’s also a fraction faster, although that’s largely irrelevant.

Bu using a reference to, you can, for example, select one of the handlers, choose Edit -> AppleScriptObjC -> Copy as Standalone Code, and when you paste the required current application's will have been inserted for you, to get around the problem of copying code from one script to another.

(That said, I’m not sure that making up your own enums like NSCapsLockMask is a very good idea. It’s NSAlphaShiftKeyMask.)


(Phil Stokes) #4

I think it’s OK if you use your own naming convention to avoid confusion down the line.
e.g.,
BKCapsLockMask :slight_smile:


(Shane Stanley) #5

You can of course use what you like. But if you make up your own, Copy as Standalone Code isn’t going to work.


#6

I did remember script objects being mentioned in “Everyday AppleScriptObjC” but it has become a dimer memory by now. I looked at the book again and still am not sure why the script object caused a problem. I did find places where “Everyday AppleScriptObjC” mentioned how script objects can be helpful in ASObj-C.

Now I am wondering when can’t a script object be used in ASObj-C.

Are you referring to “AppleScript standalone” or “ASObj-C standalone”? I looked in “Everyday AppleScriptObjC” but it didn’t talk about this, or more specially the word “standalone” showed up twice in a search of the book and it did not talk about this in either place.

I’m not sure what kind of data structure you are talking about. I have always though of AppleScript’s “a reference to” as being analogous to a pointer but I am not sure what that become when used with AppleScriptObjC.

I am not aware of any place I can actually look at a detailed explanation of what goes on when the bridge converts things. Another question is does the “a reference to” ever get converted to something different by the bridge in your example? I’m trying to get a general understanding of what you are saying and a general understanding of why your solution worked so I don’t just run into this problem again in a different form and get lost again.

Bill


(Shane Stanley) #7

I’m not saying they can’t be used — I’m saying that in some situations they can cause problems for Script Debugger’s debugging mode. Because debugging involves storing script objects, it becomes problematic if those objects contain pointers.

I’m guessing that somehow relates to your stability issues, but it’s really only a guess: I can’t reproduce them. On that basis, I was simply pointing out that your use of a script object simply to return a result was a potential liability.

I’m referring to the Script Debugger command Copy as Standalone Code. You can read an explanation of what it does in Script Debugger’s Help.

You’re over-thinking things. When Copy as Standalone Code sees a property that use a reference to and a number, it knows to treat it as a likely enum. It’s not used as any kind of a data structure: it’s a syntax convention.

There isn’t one. But for the purposes of this discussion, it’s entirely irrelevant.

No. Script Debugger could have used some other convention or marker, like a particular comment. It was chosen because it’s consistent with how classes and constants are declared as properties.