0
votes

Starting with Mac OS 10.9 Mavericks, Apple requires that Assistive Access must be enabled explicitly for each application that wants to use it.

I have written the following AppleScript function to deal with this, for a given application. The script works for me but it has 2 flaws:

  1. When the operating system is running in a language other than English, the hard-coded button names will be wrong, and the script will fail. How can I discover what language the OS is running in, and what names the "Click the lock to make changes." button will have in that language? Alternatively, is there a way to determine whether this button is in the locked, authenticating or unlocked state, without reading the name of the button?

  2. The script uses a tight repeat loop while it is waiting for the user to enter an admin username and password. Is there a better strategy I can use to wait until the dialog has been been successfully dismissed?

====

set output to allowAssistiveAccessFor("Skype")

if (the |quitWhenDone| of output) then
  tell application "System Preferences" to quit
end if

on allowAssistiveAccessFor(applicationName)
    set quitWhenDone to not (application "System Preferences" is running)
    set output to {quitWhenDone:quitWhenDone}

    tell application "System Preferences"

        activate
        reveal anchor "Privacy_Accessibility" of pane id "com.apple.preference.security"

        tell application "System Events"
            tell process "System Preferences"

                -- Find the table that contains the application icons and checkboxes
                try
                    set appTable to table 1 of scroll area 1 of group 1 of tab group 1 of window "Security & Privacy"
                on error errorMessage
                    return output & {state:-1, message:errorMessage}
                end try

                set total to the number of rows of appTable

                -- Find the row that refers to applicationName 
                repeat with rowNumber from 1 to total
                    if (name of UI element 1 of row rowNumber of appTable = applicationName) then
                        set appCheckbox to checkbox 1 of UI element 1 of row rowNumber of appTable
                        if (value of appCheckbox as boolean) then
                            -- Assistive access is already enabled for this application
                            return output & {state:0, message:"Already enabled"}

                        else
                            -- Click the “Click the lock to make changes.” button.
                            if exists button "Click the lock to make changes." of window "Security & Privacy" then
                                click button "Click the lock to make changes." of window "Security & Privacy"

                                -- The user will now have to enter an admin password. This can take some time.
                                -- The name of the button will change to "Authenticating"...
                                set unlocking to button "Authenticating…" of window "Security & Privacy"
                                repeat while exists unlocking
                                end repeat
                                -- ... and then to "Click the lock to prevent further changes." ... unless the user cancelled

                                if exists button "Click the lock to make changes." of window "Security & Privacy" then
                                    return output & {state:-1, message:"User cancelled"}
                                end if
                            end if

                            -- Click the <applicationName> checkbox.
                            -- If we had to unlock the Security & Privacy pane, then an immediate click might not have
                            -- an effect. Try as many times as possible for 1 second, and give up if unsuccessful
                            set failMessage to "Cannot allow the " & applicationName & " application to control your computer"
                            set endDate to (current date) + 1.0 -- 1 second from now

                            repeat
                                try
                                    if ((current date) > endDate) then
                                        -- Time's up
                                        return output & {state:-1, message:failMessage}

                                    end if

                                    click appCheckbox

                                    if (value of appCheckbox as boolean) then
                                        return output & {state:0, message:"Success"}
                                    end if

                                on error errorMessage
                                    -- Something dreadful happened. Keep trying until time is up
                                end try
                            end repeat
                        end if
                    end if
                end repeat
            end tell
        end tell
    end tell

    return output & {state:-1, message:"Application " & applicationName & " not found"}
end allowAssistiveAccessFor
2

2 Answers

2
votes

Instead of using the window and button names, you can refer to the window as “window 1” and the lock button as “button 4.” Then it shouldn’t matter what language the system is using.

tell application "System Preferences"
    activate
    reveal anchor "Privacy_Accessibility" of pane id "com.apple.preference.security"
    tell application "System Events"
        tell process "System Preferences"
            tell button 4 of window 1 to click
        end tell
    end tell
end tell

Once the user has authenticated, the name of button 4 changes. So you could loop until you see that change. The following is not a perfect solution, because it only works on English language systems, but maybe it helps to get you a step closer.

tell application "System Preferences"
    activate
    reveal anchor "Privacy_Accessibility" of pane id "com.apple.preference.security"
    tell application "System Events"
        tell process "System Preferences"
            tell button 4 of window 1 to click
            with timeout of 180 seconds
                repeat until button "Click the lock to prevent further changes." of window 1 exists
                end repeat
                display dialog "Preference pane unlocked." with title (the name as text) buttons {"OK"} default button "OK" giving up after 60
            end timeout
        end tell
    end tell
end tell
0
votes

This version of my script works regardless of what language the operating system is running in. Tested in French and Russian. It still uses a tight repeat loop for checking if the user has closed the authentication dialog, but this does not seem to affect performance.

set output to allowAssistiveAccessFor("skype")

if (the |quitWhenDone| of output) then
    tell application "System Preferences" to quit
end if

get output


on allowAssistiveAccessFor(applicationName)
    set quitWhenDone to not (application "System Preferences" is running)
    set output to {quitWhenDone:quitWhenDone}

    tell application "System Preferences"

        reopen -- to ensure that the application will be open and window 1 will contain a tab group
        activate

        -- Open the Accessibility pane of the Security & Privacy Preferences
        reveal anchor "Privacy_Accessibility" of pane id "com.apple.preference.security"

        -- The "Security & Privacy" window and its contents are only available through System Events
        tell application "System Events" to tell process "System Preferences"

            -- Find the table that contains the application icons and checkboxes. This will be in
            -- window 1 which is called "Security & Privacy" in English. If assistive access is
            -- not enabled for AppleScript, then we will have no access to any window.
            try
                set securityAndPrivacyWindow to window 1
                set appTable to table 1 of scroll area 1 of group 1 of tab group 1 of securityAndPrivacyWindow

            on error errorMessage
                return output & {state:-1, message:errorMessage}
            end try

            -- If we get here, then the assistive access is enabled for AppleScript.
            -- Let's find the row that refers to applicationName 
            set total to the number of rows of appTable
            repeat with rowNumber from 1 to total
                if (name of UI element 1 of row rowNumber of appTable = applicationName) then

                    -- We have found the row containing the applicationName checkbox
                    set appCheckbox to checkbox 1 of UI element 1 of row rowNumber of appTable

                    if (value of appCheckbox as boolean) then
                        -- Assistive access is already enabled for this application
                        return output & {state:0, message:"Already enabled"}

                    else
                        (*  Attempt to click on appCheckbox. If its value changes then the
                        Security & Privacy window was already unlocked. If it fails, then we will
                        need to ask the user to enter an admin name and password
                    *)

                        click appCheckbox

                        if (value of appCheckbox as boolean) then
                            return output & {state:0, message:"Success"}
                        end if

                        (*  If we get here, then the click on appCheckbox had no effect, presumably
                        because this window is locked. We need to simulate a click on the 
                        “Click the lock to make changes.” button. This may have different names in
                        different languages. All we know for certain is:
                        •   It is button 4 (in Mavericks at least)
                        •   Its name will change to something meaning "Authenticating…" when it is
                            clicked
                        •   If it was in its locked state:
                            -   a dialog will now show, and it will take the user a significant amount of
                                time to respond to the dialog
                            -   Its name will change again when the user clicks on one of the buttons
                                in the dialog
                            -   If its name reverts to the name it had before, then the user canceled
                                the dialog
                            -   If its name becomes something new, then the user successfully
                                entered an admin name and password
                        •   If it were in its unlocked state, it would immediately lock and its name would
                            not change again, so we would wait forever in the `repeat while` loop
                            However, if it were in its unlocked state then we would already have changed
                            the state of the applicationName checkbox, so we would not be here.
                        *)

                        set lockButton to button 4 of securityAndPrivacyWindow
                        click lockButton

                        -- The name of the button will have changed to something like "Authenticating"...
                        set unlocking to button 4 of securityAndPrivacyWindow

                        -- The user will now have to enter an admin password. This can take some time.
                        repeat while exists unlocking
                            -- The user has not yet clicked on either the Cancel or the Unlock button yet
                        end repeat

                        (*  The user has closed the dialog. If s/he clicked Cancel then:
                            •   The original name of the button will have been restored
                            •   We cannot continue
                        *)
                        if exists lockButton then
                            return output & {state:-1, message:"User canceled"}
                        end if

                        -- If we get here, we can simulate a click on the <applicationName> checkbox.
                        click appCheckbox

                        if (value of appCheckbox as boolean) then
                            return output & {state:0, message:"Success after authentication"}
                        else
                            return output & {state:-1, message:"Failure after authentication"}
                        end if
                    end if
                end if
            end repeat
        end tell
    end tell

    return output & {state:-1, message:"Application " & applicationName & " not found"}
end allowAssistiveAccessFor