Search code examples
toggleautohotkeyhotkeys

Toggle Multiple Hotkeys with Loop


I'm somewhat new to AutoHotKey. I made a toggleable macro that maps the WASD keys to the arrow keys.

The original code works but I'm trying to enable the hotkeys with a loop which is currently giving me a Nonexistent Hotkey error. How can I fix this loop, so that it works like the hard-coded nightmare below it?

for i in macros {
    Hotkey, % keys[i], % macros[i], % state ; <<< This line is causing the error
}

arrowsActive := 0
; CTRL + WASD --> arrow keys
Enter::
    arrowsActive := not arrowsActive
    macros := [arrowUp1, arrowLeft1, arrowDown1, arrowRight1, arrowUp2, arrowLeft2, arrowDown2, arrowRight2]
    keys := [W, A, S, D, W, A, S, D]
    state = Off
    if arrowsActive {
        state = On
    } 
    for i in macros {
        Hotkey, % keys[i], % macros[i], % state
    }
    ;Hotkey, W, arrowUp1, state       
    ;Hotkey, A, arrowLeft1, state     
    ;Hotkey, S, arrowDown1, state     
    ;Hotkey, D, arrowRight1, state    
    ;Hotkey, W Up, arrowUp2, state
    ;Hotkey, A Up, arrowLeft2, state
    ;Hotkey, S Up, arrowDown2, state
    ;Hotkey, D Up, arrowRight2, state
Return
arrowUp1: 
    send, {Up Down}
Return
arrowUp2: 
    send, {Up Up}
Return
arrowLeft1:
    send, {Left Down}
Return
arrowLeft2: 
    send, {Left Up}
Return
arrowDown1: 
    send, {Down Down}
Return
arrowDown2: 
    send, {Down Up}
Return
arrowRight1: 
    send, {Right Down}
Return
arrowRight2: 
    send, {Right Up}
Return

Solution

  • The root of your problems seem to be with understanding the legacy vs expression syntax in AHK v1. Here is also a previous answer of mine about legacy syntax and expression syntax. You might find some help from it.

    But now to the actual problems:

    macros := [arrowUp1, arrowLeft1, arrowDown1, arrowRight1, arrowUp2, arrowLeft2, arrowDown2, arrowRight2]
    keys := [W, A, S, D, W, A, S, D]
    

    You're in an expression here, so what you're trying to do is create an array that includes values from the variables arrowUp1, arrowLeft1, ..., W, A,... But those variables don't exist, so you end up creating two arrays that are full of absolutely nothing. Just empty elements.
    What you were trying to do is

    macros := ["arrowUp1", "arrowLeft1", ...
    keys := ["W", "A", ...
    

    Also in your keys array, you didn't specify w up, a up,... for the second half of the keys.

    Other than that, it would work. But of course, the implementation could be a lot better.
    Let's make it better:

    remaps := { w: "Up"
              , a: "Left"
              , s: "Down"
              , d: "Right" }
    
    Enter::
        ;store this variable's value in the global scope, so it can be referred to
        ;on the following runs of this hotkey subroutine
        global arrowsActive := !arrowsActive
    
        for key, arrowKey in remaps
        {
            loop, 2
            {
                ;first loop for hotkeys when pressing down
                ;second loop for hotkeys when releasing keys
    
                keyFunction := Func("SendKey").bind(arrowKey, (A_Index - 1) ? true : false)
                Hotkey, % key ((A_Index - 1) ? " Up" : ""), % keyFunction, % arrowsActive ? "On" : "Off"
            }
        }
    return
    
    SendKey(key, release := false)
    {
        SendInput, % "{" key (release ? " Up" : "") "}"
    }
    

    Useful documentation links to understand the code:

    The answer also utilizes the fact that you shouldn't need to specify a Down hotkey. If you have e.g. hotkeys a and a up, the hotkey a gets called everything Windows' repeat key functionality triggers and repeats a key while you hold it down. And then a up gets called when a is released.

    If something else is unclear about the answer, feel free to ask.