3

Prior to upgrading to Yosemite, I used Keyboard Maestro to invoke this Applescript with a shortcut:

tell application "System Preferences"
    reveal pane "com.apple.preferences.Bluetooth"
end tell
tell application "System Events" to tell process "System Preferences"
    click button 6 of window 1
end tell
quit application "System Preferences"

It behaved just as expected, toggling bluetooth whenever I pressed my shortcut.

It's no longer working with Yosemite though, I'm sure it has something do with Apple changing the System Preferences pane or the order of icons, but I'm not sure what to change. The Keyboard Maestro shortcut is invoking the file, because I hear the sound I had assigned to it, so it's definitely something with the script.

Here's what I'm getting under "Replies" when I run this in Apple's Script Editor:

tell application "System Preferences"
    reveal pane "com.apple.preferences.Bluetooth"
        --> missing value
end tell
tell application "System Events"
    click button 6 of window 1 of process "System Preferences"
        --> button 6 of window "Bluetooth" of application process "System Preferences"
end tell
tell application "Script Editor"
    quit
end tell

Updates:

It's definitely not the pane that's causing the problem. To debug, I changed the code to:

tell application "System Preferences"
    set current pane to pane id "com.apple.preferences.bluetooth"
end tell

And it correctly opens the Bluetooth pane. Now all that's left is to figure what kind of action I want to run on this pane:

enter image description here

Update on solutions: Thanks fartheraway! Both of markhunte's and fartheraway's solutions worked for me, but I chose the latter because it was more similar to my code. I guess you can't choose two "best" answers. I wish there'd be a solution to make it work without bringing up the preferences pane (visually) like my script did with Mavericks, but these two should be good enough.

6
  • did you try to reassign the shortcut?
    – Ruskes
    Oct 22, 2014 at 17:42
  • No, and I've updated my question with this as well: "The shortcut itself is invoking the file, because I hear the sound I had assigned to it, so it's definitely something with the script."
    – zerohedge
    Oct 22, 2014 at 17:44
  • Just tried your script (part 1) on my Mavericks and get same error (missing value)! So it is not Yosemite !
    – Ruskes
    Oct 22, 2014 at 18:01
  • Well, you obviously got the same result because something has been changed in Yosemite: Either the pane's name or reference, or the index for the button that needs to be clicked. I just can't find out which.
    – zerohedge
    Oct 22, 2014 at 18:25
  • I do NOT have Yosemite! I used your script on my Mavericks (10.9.5) with same problem showing. It does not work on my Mavericks.
    – Ruskes
    Oct 22, 2014 at 18:46

6 Answers 6

1

Updated/Better Answer:

1) This new script doesn't flash.

2) For reasons unbeknownst to man and logic (or just me), applescript sometimes/almost-always fails to Turn Bluetooth Off, if the System Preference window is in the background. Instead of turning off, what actually occurs is that Bluetooth immediately re-enable itself, so the pane is in a fresh state: it's ON, but no connections.

To overcome that, one way to to bring SysPref to the front, as in the original answer. Or, run a loop that click the button again (or for a 3rd time) until Bluetooth is really off. That's why there are two variables and a loop in the script. This should make the script more reliable. statName variable records the original status. Loop will continue clicking the button until status has changes. failSafe makes sure the script will not run forever in case of error. All at the cost of aesthetics of the code.

tell application "System Events"

    tell process "System Preferences"
        activate
    end tell

    tell application "System Preferences"
        set current pane to pane "com.apple.preferences.Bluetooth"
    end tell

    tell process "System Preferences"

        set statName to name of button 3 of window 1 as string
        set failSafe to 0

        repeat until statName is not name of button 3 of window 1 as string ¬
            or failSafe is 10
            click button 3 of window 1
            set failSafe to failSafe + 1
            delay 0.1
        end repeat

    end tell

    tell application "System Preferences"
        quit
    end tell

end tell

Original Answer:

tell application "System Preferences"
    activate --Change 1/2
    reveal pane "com.apple.preferences.Bluetooth"
end tell
tell application "System Events" to tell process "System Preferences"
    click button 3 of window 1 --Change 2/2
end tell
quit application "System Preferences"

From Accessibility Inspector:

enter image description here

button 3 in the the no.6 item on the list. The 6th button is no.11 in the list. When you call button 6 Preference Window goes Genie. I guess Mavericks had all the buttons bunched up at the front.

5
  • how did you get the list?
    – Ruskes
    Oct 22, 2014 at 19:00
  • Thank you, but this is only working once. It will turn Bluetooth on upon login when I execute the file, but it won't turn it off, nor will it turn it on after I'd turn it on manually.
    – zerohedge
    Oct 22, 2014 at 19:08
  • Thank you. Is there any way to make it work without showing the Preference pane at all?
    – zerohedge
    Oct 22, 2014 at 19:25
  • Hmm, I wonder why it worked with Mavericks then. Anyway, please modify your answer with the activate comment so I can vote it up and choose it as "best".
    – zerohedge
    Oct 22, 2014 at 20:07
  • I see you've added —change1/2 and 2/2 in there, is that something I should also have? I assume it's just for people to know where you made changes but it might be confusing to other people who'll just copy-paste the answer.
    – zerohedge
    Oct 22, 2014 at 20:30
2

I've learned a lot here, I hope this contribution helps someone too! I found using "launch" instead of "activate" will open the app visibly, but not as the foreground window. The other trick, or at least "neat thing I learned recently was possible", is using an empty repeat loop to wait for the window to load (and so the button to exist) instead of a "delay" value, which I also use to verify the change worked before showing a notification. The rest of my code is about retaining System Preference's state if it was open already, or quitting it, otherwise.

    set bundleID to "com.apple.systempreferences"

-- Check for System Preferences running already
tell the application "System Events" to set runningApps to (bundle identifier of every application process)
if bundleID is in runningApps then
    set stayOpen to true
else
    set stayOpen to false
end if

tell application id "com.apple.systempreferences"
    -- Problem with this setting is that the toggle doesn't work if the prefPane is open in the background — the window /must/ be visible
    if not (stayOpen) then launch

    -- If it's already running, save the current prefPane for later
    if (stayOpen) then set prevPane to current pane

    set current pane to pane id "com.apple.preferences.bluetooth"
end tell

tell the application "System Events"
    -- An empty repeat loop to keep checking for the window
    -- Here I am lazy and don't use the identifier
    repeat until window "Bluetooth" of process "System Preferences" exists
    end repeat

    tell window "Bluetooth" of process "System Preferences"
        if button "Turn Bluetooth Off" exists then
            -- Click and wait for it to change, then send a notification
            click button "Turn Bluetooth Off"
            repeat until button "Turn Bluetooth On" exists
            end repeat
            display notification "Bluetooth Off"
        else
            click button "Turn Bluetooth On"
            repeat until button "Turn Bluetooth Off" exists
            end repeat
            display notification "Bluetooth On"
        end if
    end tell

end tell

tell application id "com.apple.systempreferences"
    if (stayOpen) then
        if prevPane is not missing value then set current pane to prevPane
    else if not (stayOpen) then
        quit
    end if
end tell
2

Simple toggle on or off that does not need to check state first.

property thePane : "com.apple.preferences.bluetooth"

tell application "System Preferences"
    activate
    set the current pane to pane id thePane
    --delay 1
end tell

tell application "System Events"
    tell application process "System Preferences"
        try
            click button "Turn Bluetooth Off" of window "Bluetooth"
        on error
            click button "Turn Bluetooth On" of window "Bluetooth"
        end try
    end tell
end tell

tell application "System Preferences" to quit
2
  • Thank you, this one does work for toggling. But unlike the previous script, which didn't bring up system preferences at all — this one leaves the Bluetooth pane visible at the end of the process.
    – zerohedge
    Oct 22, 2014 at 19:15
  • @zerohedge added the quit call. Also you can take out the delay if you do not need it.
    – markhunte
    Oct 22, 2014 at 19:15
1

Here is my answer:

tell application "System Preferences"
    reveal pane id "com.apple.preferences.Bluetooth"
    -- activate

    set the current pane to pane id "com.apple.preferences.Bluetooth"

    try
        tell application "System Events" to tell process "System Preferences"
            click button "Turn Bluetooth Off" of window "Bluetooth"

            click button "Turn Bluetooth Off" of sheet 1 of window "Bluetooth" of application process "System Preferences" of application "System Events"
        end tell

        delay 1

    on error
        tell application "System Events" to tell process "System Preferences"
            click button "Turn Bluetooth On" of window "Bluetooth"
            quit
        end tell

    end try

end tell
1

Here's a simple toggle Bluetooth script that uses blueutil (available via Homebrew), no UI scripting. Adjust the blueutil variable as needed to point to the blueutil binary if you're not installing via Homebrew. This is based loosely on an old script I had lying around which included Growl notifications and was probably originally from http://macosxhints.com (RIP).

set blueutil to "/usr/local/bin/blueutil"
set powerStatus to do shell script blueutil & " power"

if powerStatus is "1" then
    do shell script blueutil & " power 0"
else if powerStatus is "0" then
    do shell script blueutil & " power 1"
end if
0

AppleScript Solution

Steps

  1. Open Automater.app.
  2. Type Run AppleScript in the search bar and select to open a new script window.
  3. Set Workflow receives to no input in any application in the settings at the top of the screen.
  4. Copy & paste the scripts below and save the file.
  5. Create a keyboard shortcut: System Preferences > Keyboard > Shortcuts > Services > The new script shows under General under the saved name. > Set a shortcut.

macOS Big Sur 11.5

Bluetooth Toggle.workflow

tell application "System Events"
    tell process "ControlCenter"
        set BluetoothButton to menu bar item "Bluetooth" of menu bar 1
        click BluetoothButton
        delay 1
        set OnSwitch to checkbox "Bluetooth" of group 1 of window "Control Center"
        click OnSwitch
    end tell
    key code 53
end tell

See: Setting Bluetooth with AppleScript in Big Sur - r/applescript, 12/1/20

macOS Monterey 12.0.1

Bluetooth Toggle.workflow

tell application "System Events"
    tell application process "Control Center"
        click menu bar item "Bluetooth" of menu bar 1
        tell window "Control Center"
            try
                click checkbox "Bluetooth"
            on error
                click checkbox "Bluetooth"
            end try
        end tell
    end tell
    key code 53 -- # escape key
end tell

See: AppleScript Error - Can’t get group 1 of window "Control Center" - StackExchange, 11/10/21

Third-Party

For macOS Big Sur, Enrique Scherer's answer no longer works. However, the blueutil utility has been updated and can be installed for example from homebrew.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .