autohotkey

Reassign a keyboard shortcut, but also disable the original shortcut: Autohotkey script not working consistently


In Windows 10, I use the Dvorak keyboard layout. That means common keyboard shortcuts are in different places than usual, e.g. ctrl+v and ctrl+w are right next to each other, and x/c/v are not altogether.

I'm trying to use Autohotkey to "reassign" shortcuts. As a simple example, ctrl+c to ctrl+j.

Simply adding a shortcut:

^j::^c

But, that leaves ctrl+c itself active. ChatGPT says I can do:

^j::^c
^c::return

But, that just creates a chain that ultimately exits doing nothing.

I had a thought that led to this:

HandleCtrlQOrX(key) {
    if (key = "x") {
        MsgBox "Pressed x"  ; Adding for debugging
        return
    }
    Send "^x"  ; Send ctrl+x only if ctrl+q was pressed
}
^q::HandleCtrlQOrX("q")
^x::HandleCtrlQOrX("x")

HandleCtrlJOrC(key) {
    if (key = "c") {
        MsgBox "Pressed c"  ; Adding for debugging
        return
    }
    Send "^c"  ; Send ctrl+c only if ctrl+j was pressed
}
^j::HandleCtrlJOrC("j")
^c::HandleCtrlJOrC("c")

HandleCtrlKOrV(key) {
    if (key = "v") {
        MsgBox "Pressed v"  ; Adding for debugging
        return
    }
    Send "^v"  ; Send ctrl+v only if ctrl+k was pressed
}
^k::HandleCtrlKOrV("k")
^v::HandleCtrlKOrV("v")

Logically, it seems like it might end up the same as above, depending what `Send "^c" actually does. But, it worked.

However... The more I use it, the more I see it only half works. It will work for a few minutes. Then, it will have a spell of seeming to act like I actually pressed ctrl+c. It does trigger the MsgBox during these times. Eventually, it goes back to working as I'd hoped, and also stops triggering the MsgBox.

NOTE: Since, q/j/k that I'm reassigning to are actually in the same spots on the keyboard in Dvorak as x/c/v in qwerty, I wondered if something was confusing or intermingling Dvorak and qwerty layouts. So, I tried assigning to different characters, but still get the MsgBox.

Can this script not actually work as I'm hoping?

(I know there is also this other question already: How do I change shortcut keys with autohotkey? But it doesn't address disabling the "originals".)

Also, there might be alternatives. For example:

^d::!F4
^w::return

But, alt+F4 is not strictly the same as ctrl+w, e.g. in VS Code or Google Chrome ctrl+w closes individual tabs, not the entire window.

If there is another way to accomplish sending ctrl+w while also disabling any action when pressing ctrl+w itself, that is perfectly acceptable, too.


Solution

  • Try this:-

    ; Edit: I discovered this doesn't work properly, see the edit
    ; given much further below.
    
    FuncSend(thiskey) {
        SendInput thiskey
    }
    
    SendAlt(slct) {
        Hotkey slct,, "Off" ;Disable main hotkey
        Hotkey "~" . slct, FuncSend(slct), "On" ;Enable temp. hotkey
        Send slct
        Hotkey "~" . slct,, "Off" ;Disable temp. hotkey
        Hotkey slct,, "On" ;Enable main hotkey
    }
    
    HandleCtrlQOrX(key) {
        if (key = "x") {
            MsgBox "Pressed x"  ; Adding for debugging
            return
        }
        SendAlt "^x"  ; Send ctrl+x only if ctrl+q was pressed
    }
    
    HandleCtrlJOrC(key) {
        if (key = "c") {
            MsgBox "Pressed c"  ; Adding for debugging
            return
        }
        SendAlt "^c"  ; Send ctrl+c only if ctrl+j was pressed
    }
    
    HandleCtrlKOrV(key) {
        if (key = "v") {
            MsgBox "Pressed v"  ; Adding for debugging
            return
        }
        SendAlt "^v"  ; Send ctrl+v only if ctrl+k was pressed
    }
    
    ^q::HandleCtrlQOrX("q")
    ^x::HandleCtrlQOrX("x")
    ^j::HandleCtrlJOrC("j")
    ^c::HandleCtrlJOrC("c")
    ^k::HandleCtrlKOrV("k")
    ^v::HandleCtrlKOrV("v")
    

    You can arrange the function definitions however you want.
    Explained (for the ^j and ^c case):-

    When ^j is pressed:-

    1. Function HandleCtrlJOrC receives "j"
    2. The if (key = "c") test failed; Interpreter moves on
    3. Function SendAlt receives "^c"
    4. Hotkey ^c gets disabled; temporary hotkey ~^c (watch the tilde) gets enabled, action is function FuncSend with slct variable
    5. Send presses ^c; FuncSend receives "^c"; SendInput presses ^c (I think FuncSend is useless here, but I really don't know…)
    6. Temporary hotkey ~^c gets disabled; hotkey ^c gets re-enabled
    7. The selected text is copied.

    When ^c is pressed:-

    1. Function HandleCtrlJOrC receives "c"
    2. The if (key = "c") test passed
    3. MsgBox opens up saying “Pressed c”
    4. Function returns; nothing else happens

    Similar case for the other hotkeys.

    So, a very overcomplicated solution, isn't it? (I have an idea for a more compact HandleCtrlJOrC function, might add later)


    Edit:- I did make a more compact HandleCtrlShortcut function, and also fixed some bugs I found later. So, the above code was bugged… 😭
    Updated code:-

    FuncSend(thiskey) {
        SendInput thiskey
    }
    
    SendAlt(slct) {
        Hotkey slct,, "Off" ;Disable main hotkey
        Send slct
        Hotkey slct,, "On" ;Enable main hotkey
    }
    
    HandleCtrlShortcut(set, downkey) {
        if (set = 1) {
            defkey := "x"
            newkey := "q"
        }
        else if (set = 2) {
            defkey := "c"
            newkey := "j"
        }
        else if (set = 3) {
            defkey := "v"
            newkey := "k"
        }
        if (downkey = defkey) {
            MsgBox "Pressed " . downkey ; Adding for debugging
        }
        else {
            SendAlt "^" . defkey ; Send the default key, NOT the input key
        }
    }
    
    ^q::HandleCtrlShortcut(1, "q")
    ^x::HandleCtrlShortcut(1, "x")
    ^j::HandleCtrlShortcut(2, "j")
    ^c::HandleCtrlShortcut(2, "c")
    ^k::HandleCtrlShortcut(3, "k")
    ^v::HandleCtrlShortcut(3, "v")
    

    Additions:-

    1. The code is now much shorter.
    2. The temporary hotkey was removed; it was unnecessary and only created new issues.
    3. It now uses “sets”, which allow you to easily add new modified keys.

    The only disadvantage to doing this is that the Windows Clipboard no longer works properly. When clicking on a menu item, it tries to send ^v to put it to the clipboard and then paste it, triggering the test MsgBox in return. So, do you really need this?


    Edit 3:- Okay, FuncSend was redundant, should've removed it muuuuuuuuuuch earlier.
    New code (again):-

    SendAlt(slct) {
        Hotkey slct,, "Off" ;Disable main hotkey
        Send slct
        Hotkey slct,, "On" ;Enable main hotkey
    }
    
    HandleCtrlShortcut(set, downkey) {
        if (set = 1) {
            defkey := "x"
            newkey := "q"
        }
        else if (set = 2) {
            defkey := "c"
            newkey := "j"
        }
        else if (set = 3) {
            defkey := "v"
            newkey := "k"
        }
        if (downkey = defkey) {
            MsgBox "Pressed " . downkey ; Adding for debugging
        }
        else {
            SendAlt "^" . defkey ; Send the default key, NOT the input key
        }
    }
    
    ^q::HandleCtrlShortcut(1, "q")
    ^x::HandleCtrlShortcut(1, "x")
    ^j::HandleCtrlShortcut(2, "j")
    ^c::HandleCtrlShortcut(2, "c")
    ^k::HandleCtrlShortcut(3, "k")
    ^v::HandleCtrlShortcut(3, "v")
    

    Additions:-

    1. Code is now much shorter. That's basically it.