Search code examples
macosautomationapplescriptsystemui-automation

Applescript System Preferences automation


I'm working on automating setting system preferences, but I have a problem. This code should select Wi-Fi tab but scroll area 1 does not exist unless I click any element that belongs to scroll area manually. I tried emulating click with many external programs but even then I can't access scroll area

tell application "System Preferences"
    activate
    reveal pane id "com.apple.preference.dock"
end tell
tell application "System Events" to tell application process "System Preferences"
    delay 1

    tell scroll area 1 of window 1
        select row 3 of outline 1
    end tell
end tell

Is there any other way to change Dock & Menu Bar settings or just to access scroll area items?

Edit: The end goal is to hide Wi-Fi icon from menu bar.


Solution

  • The end goal is to hide Wi-Fi icon from menu bar.


    UI Scripting of System Preferences in macOS Big Sur has become a nightmare, as many of the methods that used to work in previous versions of macOS just no longer do in macOS Big Sur. Many UI elements report Parent does not report element as one of its children when using Accessibility Inspector of Xcode, which then make it impossible to communicate with them. Or some code may work one time and then not the next. I wrote some code that opened to Wi-Fi and clicked the Show in Menu Bar checkbox. It worked a few times and now it doesn't.

    The original code I wrote which sporadically worked I'll not post, however, the following example AppleScript code does consistently work as tested under macOS Big Sur 11.4, albeit it is what I consider kludgy UI Scripting, as it's visible on screen, is prone to failure due to timing issues, or if the hierarchical UI element structures change due to macOS updates/upgrades.

    The example AppleScript code, shown below, was tested in Script Editor under macOS Big Sur 11.4 with Language & Region settings in System Preferences set to English (US) — Primary and worked for me without issue1.

    • 1 Assumes necessary and appropriate setting in System Preferences > Security & Privacy > Privacy have been set/addressed as needed.

    This script requires that the Use keyboard navigation to move focus between controls checkbox is checked on the System Preferences > Keyboard > Shortcuts tab, and as coded, the script checks its status and toggles the checkbox, as necessary, based on its current status.

    This script also first checks to see if the Wi-Fi icon is shown on the Menu Bar and if not, then halt execution of the script, as its purpose is to act only if it is shown on the Menu Bar.



    Example AppleScript code:

    --  # Get the fully qualified POSIX pathname of the target .plist file.
    
    set thePropertyListFilePath to ¬
        the POSIX path of ¬
            (path to preferences from user domain as string) & ¬
        "com.apple.controlcenter.plist"
    
    -- Get the value of 'NSStatusItem Visible WiFi' to determine if the
    -- Wi-Fi icon is showing on the Menu Bar, and if it's not, then halt
    -- execution of the script, as its purpose is to act only if it is.
    
    tell application "System Events" to ¬
        tell the property list file thePropertyListFilePath to ¬
            set |Wi-Fi Menu Bar Icon Status| to the value of ¬
                the property list item ¬
                    "NSStatusItem Visible WiFi"
    
    if |Wi-Fi Menu Bar Icon Status| is false then return
    
    
    --  # Check to see if System Preferences is 
    --  # running and if yes, then close it.
    --  # 
    --  # This is done so the script will not fail 
    --  # if it is running and a modal sheet is 
    --  # showing, hence the use of 'killall' 
    --  # as 'quit' fails when done so, if it is.
    --  #
    --  # This is also done to allow default behaviors
    --  # to be predictable from a clean occurrence.
    
    if running of application "System Preferences" then
        try
            tell application "System Preferences" to quit
        on error
            do shell script "killall 'System Preferences'"
        end try
        delay 0.1
    end if
    
    --  # Make sure System Preferences is not running before
    --  # opening it again. Otherwise there can be an issue
    --  # when trying to reopen it while it's actually closing.
    
    repeat while running of application "System Preferences" is true
        delay 0.1
    end repeat
    
    --  # Get the fully qualified POSIX pathname of the target .plist file.
    
    set thePropertyListFilePath to ¬
        the POSIX path of ¬
            (path to preferences from user domain as string) & ¬
        ".GlobalPreferences.plist"
    
    --  # Get the value of AppleKeyboardUIMode to determine if the
    --  # 'Use keyboard navigation to move focus between controls'
    --  # checkbox is checked on the System Preferences >  
    --  # Keyboard > Shortcuts tab.
    
    tell application "System Events" to ¬
        tell the property list file thePropertyListFilePath to ¬
            set keyboardNavigation to the value of ¬
                the property list item "AppleKeyboardUIMode"
    
    if keyboardNavigation = 0 then
        --  # Check the checkbox.
        my toggleKeyboardNavagition()
    end if
    
    --  # Open System Preferences to the Dock & Menu Bar pane.
    --  # 
    --  # This UI Script needs it to be visible, hence the activate command.
    
    tell application "System Preferences"
        activate
        reveal pane id "com.apple.preference.dock"
    end tell
    
    tell application "System Events"
        set i to 0
        repeat until exists window "Dock & Menu Bar" of ¬
            application process "System Preferences"
            delay 0.1
            set i to i + 1
            if i ≥ 30 then return
        end repeat
    end tell
    
    --  # Tab to the 'Show in Menu Bar' checkbox and uncheck it.
    
    tell application "System Events"
        key code 48 -- # tab key
        delay 0.2
        key code 125 -- # down arrow key
        delay 0.2
        key code 48 -- # tab key
        delay 0.2
        key code 49 -- # spacebar
        delay 0.1
    end tell
    
    if keyboardNavigation = 0 then
        --  # Uncheck the checkbox if it
        --  # was previously unchecked.
        my toggleKeyboardNavagition()
    end if
    
    delay 0.2
    
    tell application "System Preferences" to quit
    
    
    --  # Handler(s) #
    
    
    --  # Toggles checkbox: 'Use keyboard navigation 
    --  # to move focus between controls'
    
    on toggleKeyboardNavagition()
        tell application "System Preferences"
            activate
            reveal anchor "shortcutsTab" of ¬
                pane id "com.apple.preference.keyboard"
        end tell
        tell application "System Events"
            tell front window of ¬
                application process "System Preferences"
                set i to 0
                repeat until (exists checkbox 1 of tab group 1)
                    delay 0.1
                    set i to i + 1
                    if i ≥ 30 then return
                end repeat
                click checkbox 1 of tab group 1
            end tell
        end tell
    end toggleKeyboardNavagition
    

    Notes:

    If the normal state of the Use keyboard navigation to move focus between controls checkbox is unchecked, then do not run the script immediately back to back as it takes a second or two for the value of the property list item "AppleKeyboardUIMode" in the users global preferences file to update the change. I'm mentioning this mainly for when doing testing more so than when in normal production use, as it shouldn't be an issue then.


    Note: The example AppleScript code is just that and sans any included error handling does not contain any additional error handling as may be appropriate. The onus is upon the user to add any error handling as may be appropriate, needed or wanted. Have a look at the try statement and error statement in the AppleScript Language Guide. See also, Working with Errors. Additionally, the use of the delay command may be necessary between events where appropriate, e.g. delay 0.5, with the value of the delay set appropriately.