Waiting for user to click in an Applescript

I have a situation where I need users to be able to pick colors from around their screen quickly.

I want them to use the Apple Color Picker’s eyedropper to get the colors, but I’m trying to avoid all the usual clicking involved. The script will be run from a key command.

What I have so far is this:


ignoring application responses
	delay 0.1
	set frontApp to the name of the current application
	tell application "System Events"
		tell process frontApp
			click checkbox 1 of window "Colors"
		end tell
	end tell
end ignoring

choose color

This brings up the color picker and puts it into eyedropper mode. All that first part inside the “ignoring” is just to auto-click the “eyedropper” button after the color picker dialog comes up.

The user may take any amount of time to sample the exact color they want, so I can’t close the dialog on a delay. The moment they click to sample, I want to take them back out of the color picker box. As it works now, for some reason even a keystroke on [return] or [enter] won’t even get them out of the color picker, so they have to actually target the “OK” button with the mouse, which wastes half their time.

So I just want to idle the script until they click, then on click, I’ll do the following:


tell application "System Events"
		tell process frontApp
			click button "OK"  of window "Colors" 
		end tell
	end tell

to close the window, and I can use the color returned.

Any ideas?

I’d also be fine with the script running on a down keystroke, they keep holding the key down until they click to sample, then release the key and it takes them out of the dialog, if that’s possible. I couldn’t figure that out either.

Thanks in advance for any help,

t.spoon.

I didn’t find an answer, but found a work around. Posting functioning code in case it helps anyone else:


-- this will click the "eyedropper" button on the subsequent "Choose Color" dialog once it pops up.
ignoring application responses
	delay 0.2
	set frontApp to the name of the current application
	tell application "System Events"
		tell process frontApp
			click checkbox 1 of window "Colors"
		end tell
	end tell
end ignoring


set chosenColor to choose color


-- The following is a hack to force an ICC color conversion. The previous dialog reports the RGB color back converted to the Generic RGB profile, which can be way off. So this reopens the Color Picker dialog with the default now set to the color that was just chosen. But first, it runs another set of UI clicks inside an "Ignore" and with a delay, so once the color picker pops up, it converts the profile, and then clicks the "OK" button to return the new correct RGB number.

ignoring application responses
	delay 0.2
	tell application "System Events"
		tell process frontApp
			tell menu button 1 of window "Colors"
				click
				tell menu 1
					click menu item "Device RGB"
				end tell
			end tell
			delay 0.1
			click button "OK" of window "Colors"
		end tell
	end tell
	
end ignoring

choose color default color chosenColor

The OSAX AppleScript Toolbox can wait for specific events in processes or in the window server global scope. However since El Capitan some of it’s code is deprecated. The version I’m currently writing (2.0) doesn’t have this command anymore. While the current version still supports it, the next version it doesn’t. Therefore I didn’t mention it.

What version of the OS are you running? If it’s 10.10 or later, you can take a different approach by showing the dialog via AppleScriptObjC.

To run this script in Script Editor, you need to hold the Control key while choosing Run from the Script menu (or using command-control-R). It will show the color panel with the eye-dropper selected, and it will close when you click somewhere. The value returned will be converted to device RGB space for you.

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"
use framework "AppKit"

property rgbValues : missing value

-- check we are running in foreground
if not (current application's NSThread's isMainThread()) as boolean then
	display alert "This script must be run from the main thread." buttons {"Cancel"} as critical
	error number -128
end if

set my rgbValues to missing value -- start off empty
-- get color panel
set thePicker to current application's NSColorPanel's sharedColorPanel()
current application's NSColorPanel's setPickerMode:(current application's NSWheelModeColorPanel)
-- get the action called by the eye-dropper button; hackish...
set theViews to thePicker's contentView()'s subviews()
repeat with aView in theViews
	if aView's |class|() is current application's NSButton then
		set theAction to aView's action()
		exit repeat
	end if
end repeat
-- set what happens when you click
thePicker's setTarget:me -- message will be sent to this script
thePicker's setAction:"colorPicked:" -- message will call handler of this name
-- show the panel
thePicker's orderFront:me
-- click the eye-dropper
aView's setState:(current application's NSOnState)
thePicker's performSelector:theAction

repeat -- loop until the user clicks
	if rgbValues is not missing value then exit repeat
	delay 0.01
end repeat
return rgbValues

-- gets called when you click
on colorPicked:sender
	-- get the color
	set theColor to sender's |color|()
	-- convert to correct colorspace
	set newColor to theColor's colorUsingColorSpace:(current application's NSColorSpace's deviceRGBColorSpace())
	-- get components
	set theRed to newColor's redComponent()
	set theBlue to newColor's blueComponent()
	set theGreen to newColor's greenComponent()
	-- close panel
	sender's orderOut:me
	-- set property
	set my rgbValues to {theRed * 255 as integer, theGreen * 255 as integer, theBlue * 255 as integer}
end colorPicked:

You can also use it in 10.9, but you’ll have to put it in a script library and call it from there.

Shane, that is awesome! Thanks for sharing.

I’m wondering if using the same basic approach I can:

  1. Allow the user to pick a color using the keyboard from the std AppleScript chooseList (or other method)
  2. Then the script would apply that color to the selected text/object?

IOW, I want to give the user a way to select a color without using the mouse. I don’t want the the Mac Choose Color dialog to appear, unless there is a way for the user to select the color by typing the label I have assigned to it in a custom palette.

Thanks.

For example:

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"

set AppleColors to current application's NSColorList's colorListNamed:"Apple"
set colorNames to AppleColors's allKeys() as list
set theChoice to choose from list colorNames
if theChoice is false then error number -128
set chosenColor to AppleColors's colorWithKey:(item 1 of theChoice)
set theRed to (chosenColor's redComponent()) * 256 * 256 as integer
set theBlue to (chosenColor's blueComponent()) * 256 * 256 as integer
set theGreen to (chosenColor's greenComponent()) * 256 * 256 as integer

That depends entirely on what application the text is in – you’d need to script that application.

Wow! Thanks for the incredibly quick response! 👍

My immediate use is with Evernote.

It doesn’t provide any scripting interface for formatting text. The UI just provides a simple button that brings up the color picker, then the user clicks on the color, and the close button. The EN menu simply has a “Show Colors” item, which does the same as the button.

Using the user’s color choice, how can I bring up the color picker and apply that color to selected text in Evernote?

Then you’re out of luck. Email them and tell them they need to add scripting support, and explain why.

On second thoughts, if you want to use GUI scripting to copy/paste the selection, you could use this:

use AppleScript version "2.3.1"
use scripting additions
use framework "Foundation"

-- use GUI scripting to copy selection to clipboard here
-- may need slight pause or change of active app

-- get the clipboard
set theClip to current application's NSPasteboard's generalPasteboard()
-- get list of rich texts off clipboard
set theRichTexts to (theClip's readObjectsForClasses:{current application's NSAttributedString} options:(missing value))
if (count of theRichTexts) = 0 then
	display dialog "No rich text found on the clipboard" buttons {"OK"} default button 1
	error number -128
end if
-- make an editable copy of the first item of the list so we can modify it
set theRichText to (item 1 of theRichTexts)'s mutableCopy()

-- get color choice
set AppleColors to current application's NSColorList's colorListNamed:"Apple"
set colorNames to AppleColors's allKeys() as list
set theChoice to choose from list colorNames
if theChoice is false then error number -128
set chosenColor to AppleColors's colorWithKey:(item 1 of theChoice)

-- modify text color
set theLength to theRichText's |length|()
theRichText's addAttribute:(current application's NSForegroundColorAttributeName) value:chosenColor range:({0, theLength})

-- clear clipboard and write new rich text to it
theClip's clearContents()
theClip's writeObjects:{theRichText}

-- use GUI scripting to paste into document
-- may need slight pause or change of active app

Shane,

You are absolutely amazing. Thank you a million.

There’s one issue, which I’m 99% sue is OSX because I have found about a dozen bugs in color management in OSX that I need to file. I’m getting the wrong RGB numbers off yours. It’s probably in the line:

set newColor to theColor's colorUsingColorSpace:(current application's NSColorSpace's deviceRGBColorSpace())

And you’re probably basing that on my code that said:

 click menu item "Device RGB"

But it turns out that doesn’t actually work correctly, which I’m pretty sure is a bug.

But I’m not familiar with ApplescriptObjC so I’m having trouble figuring out what my options are for messing around with it.

Here’s why I think this is Apple:

If you go here http://www.colorspire.com/rgb-color-wheel/ and enter, say, RGB {50,100,200}:

  • I can sample the swatch with the eyedropper in Photoshop and it returns RGB {50,100,200}
  • I can open OSX’s included Digital Color Meter, set it to “Display Native Values,” and it shows RGB {50,100,200}
  • If I use Applescript’s “choose color” and sample it with the eyedropper, I get RGB {38,71,191}
  • If I use Applescript’s “choose color” (on my primary display) and then change the profile on the Color Picker’s gear menu to my primary display’s monitor profile, it gets changed to the correct values, RGB {50,100,200}
  • If I use Applescript’s “choose color” (on my primary display)) and then change the profile on Color Picker’s gear menu to “Device RGB,” I get RGB {50,95,203}

I am unable to figure out what “Device RGB” on the “Choose Color” color panel actually does, but it does not seem to use the device’s profile, because you get a different result if you manually change to the device’s profile.

So, on mine, I had switched to doing this:

tell application "Image Events"
	set displayProfile to the name of the display profile of item 1 of displays
end tell

and then

click menu item displayProfile

However, it turns out my UI scripting hack is a total waste anyway, because no matter what profile I choose off that gear menu, the same RGB numbers always get returned to Applescript. So while this hack made it display the correct RGB values in the color picker, when I click “OK” it does not return the displayed values.

That the built-in “Device RGB” option doesn’t work is a serious problem, because to manually use the display profile, the script would have to also know which display was clicked on and then use the appropriate profile.

I can’t believe how hard it is to get OSX to report back the the actual RGB numbers without trying to perform an in-n-case-useful color conversion on them.

Any thoughts, anybody?

Thanks again Shane, the ApplescriptObjC code you provided is fantastic.

Shane, you are totally awesome! 👍

Thank you so very, very much for going the extra mile, no, the extra 100 miles, and providing such a detailed solution. I think many will be able to make very good use of this script.

Best Regards,
JMichaelTX

FWIW, I get slightly different values, which suggests they are device values. Try adding this code and see if it enlightens you at all:

	log (theColor's |description|() as text)
	log (theColor's colorSpace()'s |description|() as text)
	log (theColor's colorSpaceName() as text)

I come from an age before color management, and I’ve never fully groked it…

t.spoon,

Some good news. The script I posted had two bugs:

  • I was calling greenComponent() on theColor instead of newColor;

  • I had the conversion from Cocoa’s 0.0-1.0 values to 0-255 all wrong.

I’ve now fixed those, and run the script against the page you pointed to, with a result of:

{51, 100, 200}

Which strikes me as near enough, given rounding errors.

If you want to try other color spaces, insert this code to log what they return:

	set colorSpaces to current application's NSColorSpace's availableColorSpacesWithModel:(current application's NSRGBColorSpaceModel)
	set colorSpaceNames to colorSpaces's valueForKey:"localizedName"
	repeat with aName in colorSpaceNames
		set thePred to (current application's NSPredicate's predicateWithFormat:"localizedName == %@" argumentArray:{aName})
		set thisColorSpace to (colorSpaces's filteredArrayUsingPredicate:thePred)'s firstObject()
		set newColor to (theColor's colorUsingColorSpace:thisColorSpace)
		log (newColor's |description|() as text)
		log (aName as text)
		set theRed to newColor's redComponent()
		set theBlue to newColor's blueComponent()
		set theGreen to newColor's greenComponent()
		log ({theRed * 255 as integer, theGreen * 255 as integer, theBlue * 255 as integer})
	end repeat

Ah, fantastic! I’ve got to take the time to learn ASObjC.

I had caught the 0-255 mapping, because it was returning numbers that looked like 16-bit, and when I fixed them, I noticed you’d used 256 instead of 255.

But I did NOT notice the “theColor” instead of “newColor” bug, and I also am now getting results that are “close enough.” So again, super huge thanks.

Color management doesn’t have to be hard. If you’re ever interested, Real World Color Management by Bruce Fraser makes it all very clear. I used to do Color Management consulting, I have a decent idea how all this is supposed to work.

The real problems don’t come from the complexity of how color management was designed to work, it comes from ridiculous implementations. I feel like the norm for color management is to tell some engineer who knows nothing about it that they have to implement it, and they just skim some summary and wing it. There are so many things with bad, bad color management implementation. Settings that are impossible to figure out, and do ludicrous, standards and logic defying things.

I mean, the “choose color” color picker in Applescript here is a great example. 99% of the time anyone uses this for anything, the intention of the user is going to be to determine the underlying RGB numbers, but it’s actually impossible to get it to return that to Applescript. Defaulting to converting the numbers from the native values to the “Generic RGB” color space is a bizarre idea, as there is almost no chance that profile is a useful representation of anything involved with the sample.

re:

Not exactly… with the “Choose Color” color picker, when you choose the actual “device profile,” meaning the monitor profile for the monitor you’re selecting from, then the color picker always shows exactly the underlying values. That will be true for your computer or mine.

So what is it doing when you choose “Device RGB?” It’s not using the device profile. There’s no profile on the computer named “Device RGB.” I haven’t been able to figure out any color conversion it could be doing that results in the numbers shown when you choose “Device RGB.”

But all that aside, thank you so much again. Apple’s odd implementation of Color Management is certainly not your fault!

I’d assumed device RGB was a “simple” way of getting the space for the active device, but maybe not. Bruce and I occasionally played tag-team in off-topic threads on mailing lists many years ago, but he never managed to convince me that color management isn’t hard :wink:

I only noticed the bug with theColor when I ran the logging code I posted, and I noticed every conversion returned the same value for green.

Anyway, it was interesting to see it all sort of work in the end.

It works brilliantly! It’s everything the best I could have hoped for.

Oh holly cow, your name didn’t ring a bell when I was reading your posts in this thread. I just thought I’d google to see if you have a website or do programming-for-hire or what, and hit your page here:
http://www.macosxautomation.com/applescript/apps/
and suddenly realized who you are. I already bought your book back at the beginning of December, but haven’t had a chance to start reading it yet.
Well, no wonder you know ASObjC so well, you literally wrote the book on it.