Alan Alves de Oliveira
Alan Alves de Oliveira

Reputation: 713

How to create an Applescript to set a Keyboard shortcut?

If someone could give me a hand, I'm trying to automatize the creation of keyboards shortcuts for some Services.
What I got so far is

tell application "System Preferences"
    activate
    set current pane to pane id "com.apple.preference.keyboard"
    delay 1
    tell application "System Events"
        click radio button "Shortcuts" of tab group 1 of window "Keyboard" of application process "System Preferences"
        delay 1
        select row 6 of table 1 of scroll area 1 of splitter group 1 of tab group 1 of window 1 of application process "System Preferences"
        set value of text field 1 of UI element "My Service Name" of row 59 of outline 1 of scroll area 2 of splitter group of tab group 1 of window 1 of application process "System Preferences" to "⌥⌘1"
    end tell
end tell

But now I'm stuck, what I'm not being able to do is:

Does someone have a hint how can I solve this?

If is there some other way to create shortcut without the applescript will be welcome

Thank you all in advance.

Upvotes: 0

Views: 1868

Answers (2)

Ted Wrigley
Ted Wrigley

Reputation: 3174

I've restructured your script a bit, because I prefer the hierarchical 'tell block' structure (which I find it easier to read and follow), and I've put it all into a handler to generalize it. But that aside, the trick is to use where searches to look down the UI structure and find identifiable features (N.B, where is a synonym for the more conventional whose). See the comments in-script. Note that I am changing the "Show Finder search window" row in the "Spotlight" section of the keyboard shortcuts preferences, but by changing the values of the handler call you can change any preference.

To make this work, you need to know which section your service falls under, and the name of the service as it appears in the list. If you put in a text string for new_val it will treat it as though you had typed the first letter; if you put in an integer it will treat it as a key code, allowing you to use non-printing characters and function keys for shortcuts. See: list of key codes.

This script has been corrected to fix a couple of errors, and to accommodate both the hierarchical structure of the 'Services' section and and the ability to add a new shortcut by clicking the 'Add Shortcut' button. Note that if you're adding short-cut for the first time, the UI will still show the 'Add Shortcut' button until you select a different row. This is true even when adding shortcuts manually, so it's a limitation of the GUI, not the script.

-- sets the 'Send Message' shortcut to cmd-opt-A
my change_shortcut("Services", "Text", "Send Message", "a", {"command", "option"})
-- sets the 'Call' service shortcut to cmd-F6
my change_shortcut("Services", "Text", "Call", 97, {"command"})

on change_shortcut(shortcut_region, section_name, shortcut_title, new_val, special_key_list)
    tell application "System Preferences"
        activate
        set current pane to pane id "com.apple.preference.keyboard"
        delay 1
        tell application "System Events"
            tell process "System Preferences"'s window "Keyboard"'s first tab group
                click radio button "Shortcuts"
                tell first splitter group
                    set sidebar_obj to first table of first scroll area
                    tell sidebar_obj

                        (* 
                            this looks to find the first row in the sidebar that contains a static text 
                            element with the value of `shortcut_region`
                        *)

                        set sidebar_entry to first row where (its first static text's value is shortcut_region)
                        select sidebar_entry
                    end tell
                    set outline_obj to first outline of second scroll area
                    tell outline_obj

                        (* 
                            if the shortcut outline view is arranged in groups, this section 
                            finds the correct group and make sure its disclosure triangle is 
                            opened, exposing the settings within
                        *)

                        if section_name is not "" and section_name is not missing value then
                            set wanted_section_row to first row where (its last UI element's name is section_name)
                            tell wanted_section_row's second UI element
                                set disclosure_tri to first UI element whose role description is "disclosure triangle"
                                if disclosure_tri's value is 0 then click disclosure_tri
                            end tell
                            delay 0.5
                        end if

                        (* 
                            this looks to find the first row in the outline that contains two 
                            UI elements (the row's cells) the second of which contains a static text 
                            element with the value of shortcut_title
                        *)

                        set wanted_entry to first row where (its last UI element's name is shortcut_title)
                        tell wanted_entry
                            select
                            set new_shortcut_flag to false
                            tell second UI element
                                if exists button "Add Shortcut" then
                                    click button "Add Shortcut"
                                    set new_shortcut_flag to true
                                end if
                                UI elements

                                -- set up a list of special keys
                                set special_keys to {}
                                if special_key_list contains "command" then set end of special_keys to command down
                                if special_key_list contains "control" then set end of special_keys to control down
                                if special_key_list contains "option" then set end of special_keys to option down
                                if special_key_list contains "shift" then set end of special_keys to shift down

                                -- opens the text field for editing, then write or keycode in the text
                                tell first text field
                                    if not new_shortcut_flag then perform action "AXConfirm"
                                    if class of new_val is text then
                                        keystroke new_val using special_keys
                                    else
                                        key code new_val using special_keys
                                    end if
                                end tell
                                -- select the cell to submit the result
                                select
                            end tell
                        end tell
                    end tell
                end tell
            end tell
        end tell
    end tell
end change_shortcut

Upvotes: 1

user3439894
user3439894

Reputation: 7555

The only way I know of for sure that will work using UI Scripting and System Preferences > Keyboard > Shortcuts > Services (or any other category under Shortcuts) is to essentially simulate all the steps that would occur when doing it manually. Which is why I'd only use UI Scripting if there is no other way to accomplish the task at hand.

Since in your code, you are using select row 6 ... and wanting to target row 59, I'm assuming you are using macOS Mojave or macOS Catalina, and targeting the Services category, as that is the only category that would realistically have that many rows, or more, to assign a keyboard shortcut to.

The example AppleScript code, shown further below, was tested in Script Editor under macOS Mojave and macOS Catalina, as well as macOS High Sierra with one minor edit, and works as is, on my system using US English for its Language & Region settings in System Preferences.

This is also written to specifically target the Services category, as other categories require different coding.

You will need to set the value of three variables, serviceName, regularKey, modifierKeys, the latter of which is based on the list in the beginning comments of the script.

It is initially set for Import Image using keyboard shortcut ⇧⌘9 and you should test the script as is, before modifying it.

Note: Any keyboard shortcut set must be unique to any app that has focus when the keyboard shortcut is pressed.

Example AppleScript code:

--  # Call the SetChangeServicesKeyboardShortcut(serviceName, regularKey, modifierKeys)
--  # handler using the parameters as defined below:

--  # serviceName defines the name of the target service under:
--  # System Preferences > Keyboard > Shortcuts > Services

--  # regularKey defines the regular key to press.

--  # modifierKeys define the modifier keys to be pressed. 
--  # Use the value based on the list below:
--  # 
--  #   1 = {command down}
--  #   2 = {shift down, command down}
--  #   3 = {control down, command down}
--  #   4 = {option down, command down}
--  #   5 = {control down, option down, command down}
--  #   6 = {shift down, control down, command down}
--  #   7 = {shift down, option down, command down}
--  #   8 = {shift down, control down, option down, command down}
--  #   
--  # |  shift = ⇧ | control = ⌃ | option = ⌥ | command = ⌘ |
--  #   


my SetChangeServicesKeyboardShortcut("Import Image", "9", "2")



--  ##################################
--  ## Do not modify code below unless necessary, as ##
--  ## it's tokenized for the variables defined above.   ##
--  ##################################


--  ## Handlers ##

on SetChangeServicesKeyboardShortcut(serviceName, regularKey, modifierKeys)
    --  # Need to start with System Preferences closed.
    if running of application "System Preferences" then
        try
            tell application "System Preferences" to quit
        on error
            do shell script "killall 'System Preferences'"
        end try
    end if
    repeat while running of application "System Preferences" is true
        delay 0.1
    end repeat
    --  # Open System Preferences to the target pane.
    tell application "System Preferences"
        activate
        reveal pane id "com.apple.preference.keyboard"
    end tell
    --  # Navigate to Shortcuts > Services and select the
    --  # target service, then change/set its keyboard shortcut.
    tell application "System Events"
        tell application process "System Preferences"
            tell its window 1
                --  # Wait until the Shortcuts tab can be clicked.          
                repeat until exists (radio buttons of tab group 1)
                    delay 0.1
                end repeat
                --  # Click the Shortcuts tab.          
                click radio button "Shortcuts" of tab group 1
                --  # Wait until Services can be selected.                      
                repeat until exists ¬
                    (rows of table 1 of scroll areas of splitter group 1 of tab group 1 ¬
                        whose name of static text 1 is equal to "Services")
                    delay 0.1
                end repeat
                --  # Select Services.          
                try
                    select (rows of table 1 of scroll area 1 of splitter group 1 of tab group 1 ¬
                        whose name of static text 1 is equal to "Services")
                end try
                tell outline 1 of scroll area 2 of splitter group 1 of tab group 1
                    --  # Wait until the services under Services are available.                 
                    repeat until exists (row 1)
                        delay 0.01
                    end repeat
                    --  # Set focus to the first item of Services.              
                    repeat 2 times
                        key code 48 -- # tab key
                        delay 0.25
                    end repeat
                    --  # Get the name of every service under Services.             
                    set serviceNames to (get name of UI element 2 of rows)
                    --  # Get the row number of the target service under Services.              
                    set countRows to (count serviceNames)
                    repeat with i from 1 to countRows
                        if contents of item i of serviceNames is equal to serviceName then
                            set rowNumber to i
                            exit repeat
                        end if
                    end repeat
                    --  # Select the row of the target target service under Services.               
                    select (row rowNumber)
                    --  # Change/Set the keyboard shortcut of the target service under Services.                
                    if exists (button "Add Shortcut" of UI element 2 of row rowNumber) then
                        click button "Add Shortcut" of UI element 2 of row rowNumber
                        my shortcutKeystrokes(regularKey, modifierKeys)
                    else
                        key code 36 -- # return key
                        my shortcutKeystrokes(regularKey, modifierKeys)
                    end if
                    select (row 1)
                end tell
            end tell
        end tell
    end tell
    quit application "System Preferences"
end SetChangeServicesKeyboardShortcut

on shortcutKeystrokes(regularKey, modifierKeys)
    tell application "System Events"
        if modifierKeys is equal to "1" then
            keystroke regularKey using {command down}
        else if modifierKeys is equal to "2" then
            keystroke regularKey using {shift down, command down}
        else if modifierKeys is equal to "3" then
            keystroke regularKey using {control down, command down}
        else if modifierKeys is equal to "4" then
            keystroke regularKey using {option down, command down}
        else if modifierKeys is equal to "5" then
            keystroke regularKey using {control down, option down, command down}
        else if modifierKeys is equal to "6" then
            keystroke regularKey using {shift down, control down, command down}
        else if modifierKeys is equal to "7" then
            keystroke regularKey using {shift down, option down, command down}
        else if modifierKeys is equal to "8" then
            keystroke regularKey using {shift down, control down, option down, command down}
        end if
    end tell
end shortcutKeystrokes

Note: For macOS High Sierra, and possibly earlier, set repeat 2 times under the comment -- # Set focus to the first item of Services. to: repeat 3 times


Note: The example AppleScript code is just that and, sans existing 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.

Upvotes: 2

Related Questions