Help: Change the Color of the Selected Object in Preview

Hey guys!

Needed a bit of advice. What’s the best way to change the color of the selected object in Preview, per this video: http://d.pr/mHfG/TH4It1Sy

I know how to get the markup toolbar to open, but changing the colors is a challenge. Peter’s suggestion was “relative to window, pause, click” or Applescript but suggested that I ask here.

Not sure how I would do this with AppleScript, although that’d be nice and direct if possible.

Any ideas?

Thank you,

Eugene

This AppleScript works here:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "Keyboard Maestro Engine"
  set btnNum to (getvariable "zzzColorButtonNumber") as integer
end tell

tell application "System Events"
  tell application process "Preview"
    tell first window
      tell (first toolbar whose description is "toolbar")
        if value of (first checkbox whose description is "Markup") is 0 then
          tell (first checkbox whose description is "Markup")
            perform action "AXPress"
          end tell
        end if
      end tell
      tell (first toolbar whose description is "edit toolbar")
        tell (first color well whose description is "Border color")
          perform action "AXPress"
          tell first pop over
            tell (first UI element whose description is "grid")
              tell button btnNum
                perform action "AXPress"
              end tell
            end tell
          end tell
        end tell
      end tell
    end tell
  end tell
end tell

‌The script takes the color button number from a KM variable.

Here the complete macro:

_[test] Select Border Color in Preview.kmmacros (3.0 KB)

Instructions:

  1. Set the desired color button number in the green action of the macro.
  • The color buttons are the color fields in Preview’s color well. They are numbered from left to right, top to bottom.
  1. Make sure your document is open in Preview.
  2. Launch the macro.

Wow - this is truly amazing! Thank you so much Tom. Exactly what I was looking for and much appreciated. :+1::pray:

Great!

If you think a post has solved your problem you can check the “Problem solved” checkbox at the bottom of the post that solved your problem.

Hi guys,

On one of my Macs (although not the other one), I now get an error when running this AppleScript. The error is as follows. Any ideas?

osascript: OpenScripting.framework - scripting addition "/Library/ScriptingAdditions/Adobe Unit Types.osax" cannot be used with the current OS because it has no OSAXHandlers entry in its Info.plist.
/var/folders/tl/4gngpls16hs08fhcm21991jr0000gn/T/Keyboard-Maestro-Script-8B7F3714-BC0F-4D79-AEAB-1FAA4126A24E:359:375: execution error: System Events got an error: Can’t get toolbar 1 of window 1 of application process "Preview" whose description = "toolbar". Invalid index. (-1719)

Thanks again!

You have a scripting addition installed that is not compatible with your OS.

This problem has been extensively covered in another thread, starting with this post. For the Adobe Unit Types.osax see especially this post:

Hi Tom - thank you for the response! I updated the Adobe Unit Types file per that previous post, and now get this error. Any ideas?

/var/folders/tl/4gngpls16hs08fhcm21991jr0000gn/T/Keyboard-Maestro-Script-B0E3DC75-6731-41DA-AD44-07C341154E65:359:375: execution error: System Events got an error: Can’t get toolbar 1 of window 1 of application process "Preview" whose description = "toolbar". Invalid index. (-1719)

It works here, so I fear I can’t help much.

What OS version is running on the Mac where the script doesn’t work?

Thank you for the response! Looks like restarting Preview fixed it. Greatly dislike the bugs within MacOS these days, but greatly appreciate your help!

Hi everyone! I have been using this amazing solution that @Tom came up with in 2017, but now in MacOS 27, it no longer works. My friend Claude tried doing this with clicking at a certain location on screen, but that seems unreliable. Any ideas? Thank you!

Golden Gate 27.0:

After adapting the script (the big color grid is now grid 2), it correctly opens the pop over, it finds the grid, it sees the buttons (the colors in the grid), but then it cannot AXPress the button with the given index.

Test script for GG
-- Standalone; run in Script Editor

tell application "System Events"
	tell application process "Preview"
		set frontmost to true
		tell first window
			tell (first toolbar whose description is "toolbar")
				if value of (first checkbox whose description is "Markup") is 0 then
					tell (first checkbox whose description is "Markup")
						perform action "AXPress"
					end tell
				end if
			end tell
			tell (first toolbar whose description is "edit toolbar")
				tell (first color well whose description is "Fill color")
					perform action "AXPress"
					tell first pop over
						tell (UI element 2 whose description is "grid")
							--delay 1
							--UI elements
							tell button 13
								--delay 1
								perform action "AXPress"
							end tell
						end tell
					end tell
				end tell
			end tell
		end tell
	end tell
end tell

Currently I don’t know how to fix this.

I’ve noticed other oddities with scripts for other apps (like window 1 not seen by System Events, until switching to another tab and back again), so – maybe – 27.0 is just “glitchy” and we should wait for 27.1. Unless someone knows what is going on?

Thank you @Tom !

The closest that ChatGPT got is this, which works but feels pretty clunky and doesnt work as reliably as the original script. Maybe this would spark an alternative idea?

ChatGPT's solution
use scripting additions

tell application "Preview" to activate
tell application "System Events" to tell process "Preview"

    set docWindow to missing value
    
    repeat with w in windows
        try
            if value of attribute "AXIdentifier" of w is "PVDocumentWindow" then
                set docWindow to contents of w
                exit repeat
            end if
        end try
    end repeat
    
    if docWindow is missing value then error "Open a document in Preview and select the signature."
    
    set mainToolbar to first toolbar of docWindow whose description is "toolbar"
    
    set markupButton to first checkbox of mainToolbar whose description is "Markup"
    
    if value of markupButton is 0 then click markupButton
    
    repeat 30 times
        try
            set borderWell to first color well of (first toolbar of docWindow whose description is "edit toolbar") whose description is "Border Color"
            exit repeat
        end try
        delay 0.02
    end repeat
    
    perform action "AXPress" of borderWell
    
    repeat 30 times
        try
            click button "Show Colors…" of pop over 1 of borderWell
            exit repeat
        end try
        delay 0.02
    end repeat
    
    repeat 120 times
        if exists window "Colors" then exit repeat
        delay 0.03
    end repeat
    
    if not (exists window "Colors") then error "Preview’s Colors window did not open within 3.6 seconds."
    
    set colorPanel to window "Colors"
    
    perform action "AXRaise" of colorPanel
    
    set panelBody to splitter group 1 of colorPanel
    
    if not (exists pop up button 1 of panelBody) then click (first button of toolbar 1 of colorPanel whose description is "Color Sliders")
    
    repeat 30 times
        if exists pop up button 1 of panelBody then exit repeat
        delay 0.02
    end repeat
    
    if value of pop up button 1 of panelBody is not "RGB Sliders" then
        click pop up button 1 of panelBody
        repeat 30 times
            try
                click menu item "RGB Sliders" of menu 1 of pop up button 1 of panelBody
                exit repeat
            end try
            delay 0.02
        end repeat
    end if
    
    repeat 30 times
        if (count text fields of panelBody) ≥ 4 then exit repeat
        delay 0.02
    end repeat
    
    set hexField to missing value
    
    repeat with f in text fields of panelBody
        if value of attribute "AXIdentifier" of f is "hex" then set hexField to contents of f
    end repeat
    
    if hexField is not missing value then
        set value of attribute "AXFocused" of hexField to true
        set value of hexField to "0042AA"
        key code 48
    else
        my enterComponent(panelBody, "red", "0")
        my enterComponent(panelBody, "green", "0.259502")
        my enterComponent(panelBody, "blue", "0.665142")
    end if
    
    set opacityField to first text field of panelBody whose value of attribute "AXIdentifier" is "opacity"
    
    set value of attribute "AXFocused" of opacityField to true
    
    set value of opacityField to "100%"
    
    key code 48
    
    delay 0.05
    
    set resultColor to value of first color well of panelBody as text
    
    click (first button of colorPanel whose subrole is "AXCloseButton")
    
    if resultColor is not "rgb 0 0.259502 0.665142 1" and resultColor is not "rgb 0 0.258824 0.666667 1" then error "Preview returned an unexpected color: " & resultColor
    
    return "Signature blue applied."

end tell

on enterComponent(panelBody, fieldID, fieldValue)

    tell application "System Events"
        repeat with f in text fields of panelBody
            if value of attribute "AXIdentifier" of f is fieldID then
                set value of attribute "AXFocused" of f to true
                set value of f to fieldValue
                key code 48
                return
            end if
        end repeat
        error "Missing color field: " & fieldID
    end tell

end enterComponent

Please note that this is not something I'd ever use myself, as I rarely do any sort of editing in Preview. However, I thought I'd ask Claude what it could come up with using Swift and the Accessibility API. The attached macro seems to work fine in both Tahoe and Golden Gate.

This macro contains a Swift script that was 100% AI generated, using Anthropic's Claude.

You need to set the color swatch you want in the green variable box—the top left corner of the pop-up color palette is 0 (red, on my Mac). 12 would be the clear item, the top left entry below the first row of colors. And 131 is the lower right corner.

Note that this script will be notably faster if you compile it. To do that, you'd save the source of the Swift script from the macro to some temporary text file, then issue this command:

swiftc /path/to/temp/ColorPicker.swift -o swiftpicker

You'd then modify the macro to execute the newly-compiled Swift script file (swiftpicker in this case) instead of a text script, and it will run much quicker. (But it's not horridly slow in source form.)

Download Macro(s): Preview color picker.kmmacros (16 KB)

Macro screenshot

Macro notes
  • Macros are always disabled when imported into the Keyboard Maestro Editor.
    • The user must ensure the macro is enabled.
    • The user must also ensure the macro's parent macro-group is enabled.
System information
  • macOS 26.6.1
  • Keyboard Maestro v11.1.1

As I said, I won't do anything more with this, as it's not something I'd ever use, but maybe it'll be useful.

-rob.

Thanks for the Swift script, @griffman. It also works in macOS 27.

It works because of one single function:

    syntheticClick(at: CGPoint(x: frame.midX, y: frame.midY))

…inside the pressOrClick function…

func pressOrClick(_ element: AXUIElement) -> Bool {
    if axPress(element) { return true }
    guard let frame = axFrame(element) else { return false }
    syntheticClick(at: CGPoint(x: frame.midX, y: frame.midY))
    return true
}

For the first two buttons (Markup toolbar and color well), the Swift script runs the axPress(element) branch (i.e., the same as the perform action "AXPress" in the original AppleScript). For the color button itself, where the AXPress of the AppleScript and the axPress(element) of the Swift script fails, it falls back to the syntheticClick(at:….

syntheticClick function
func syntheticClick(at point: CGPoint) {
    let source = CGEventSource(stateID: .hidSystemState)
    let down = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown,
                        mouseCursorPosition: point, mouseButton: .left)
    let up = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp,
                      mouseCursorPosition: point, mouseButton: .left)
    down?.post(tap: .cghidEventTap)
    usleep(30_000)
    up?.post(tap: .cghidEventTap)
}

In the AppleScript, I already tried click and click at <button xy> before, but it fails for the color button just like AXPress does.

Siri AI (yes, such a thing exists now :flushed_face:) explains the failure as follows:

The reason your AppleScript click and click at commands are failing—while the Swift script succeeds—comes down to how macOS now handles event routing for transient UI elements like popovers.
Starting in recent macOS versions, System Events attempts to be "helpful" by resolving clicks through the Accessibility API rather than the raw window server. Because the popover explicitly advertises AXPress: none, System Events aborts the click before it even posts a mouse event.
To bypass this in pure AppleScript, you must force the OS to post a raw, hardware-level mouse event (just like the Swift script's CGEvent fallback).

And Siri also provided a working solution, by simply running the relevant function from @griffman’s Swift script inside the old AppleScript:

-- Standalone demo script for ScriptEditor

use scripting additions

set btnIdx to 73

tell application "System Events"
  tell application process "Preview"
    set frontmost to true
    tell first window
      tell (first toolbar whose description is "toolbar")
        if value of (first checkbox whose description is "Markup") is 0 then
          tell (first checkbox whose description is "Markup")
            perform action "AXPress"
          end tell
        end if
      end tell
      tell (first toolbar whose description is "edit toolbar")
        tell (first color well whose description is "Border color")
          perform action "AXPress"
          tell first pop over
            tell (UI element 2 whose description is "grid")
              tell button btnIdx
                -- perform action "AXPress" -- This fails here!
                set btnPos to its position
                my syntheticClick(item 1 of btnPos, item 2 of btnPos)
              end tell
            end tell
          end tell
        end tell
      end tell
    end tell
  end tell
end tell


on syntheticClick(x, y)
  set swiftScript to "import CoreGraphics\n" & ¬
    "let down = CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: CGPoint(x: " & x & ", y: " & y & "), mouseButton: .left)\n" & ¬
    "let up = CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: CGPoint(x: " & x & ", y: " & y & "), mouseButton: .left)\n" & ¬
    "down?.post(tap: .cghidEventTap)\n" & ¬
    "usleep(30000)\n" & ¬
    "up?.post(tap: .cghidEventTap)"
  
  do shell script "echo " & quoted form of swiftScript & " | swift -"
end syntheticClick

Here as KM macro:
Preview- Select Border Color (macOS 27).kmmacros (4.1 KB)

But honestly, you can as well use @griffman’s full-Swift script, as it will probably be faster. (In compiled form! See griffman’s instructions.)

Thank you @griffman and @Tom! I installed Griffman's macro and it worked, though I did have to add a 0.75 second pause after placing the signature before the script runs, as it was timing out without it. So glad this works now!

So the Color button might not be a real button. Which would be naughty of Apple. :slight_smile:

I thought the same, and asked Siri if she thinks missing AX actions are an intermediate thing (i.e., they may come back with one of the next OS 27 patches), or rather a trend toward trimming out “unneeded” AX features:

The removal of AXPress from elements like the color grid buttons is likely not a deliberate move to limit accessibility, but rather a side effect of how modern macOS apps are being built.

There are three main reasons you are seeing this change:

The Shift to SwiftUI and Catalyst Apple is progressively rewriting standard UI components using newer frameworks like SwiftUI. In these frameworks, a color grid isn't always treated as a collection of individual "buttons." Instead, it is often rendered as a single, unified canvas or list. The framework automatically handles the visual selection for a human clicking it, but it doesn't always automatically generate individual AXPress actions for every sub-element.
Over-reliance on "AXSelected" For grids and lists, Apple's internal guidelines often push developers toward using AXSelected (changing a state) rather than AXPress (clicking a button). Unfortunately, System Events in AppleScript has never fully supported AXSelected, leaving scripters in the dark.
Performance Optimization Generating a full accessibility tree with actions for every single tiny UI element (like every swatch in a color picker) is computationally expensive. Developers often "flatten" the accessibility tree for complex views to make the app faster, inadvertently stripping out the actions you rely on.

While it is frustrating, it points to a future where UI scripting will likely require more coordinate-based synthetic clicks—like the Swift solution we built—rather than relying on the app to properly advertise its internal actions.

Feeling a bit silly posting AI replies here, but it sounds plausible, as far as I can tell.