7

For example, in Visual Studio Express 2013, many of the formatting shortcuts have been "folded" into Ctrl+E. To comment a selection, one would hold down Ctrl, hit E, hit C, then release Ctrl.

    enter image description here

If I were sending such input, I'd write

SendInput {Ctrl Down}ec{Ctrl Up}

But how do I make that sequence into a hotkey? I tried

{Ctrl Down}EC{Ctrl Up}::
    MsgBox, "Hello, world!"
    Return

but of course, this causes a syntax error:

    enter image description here

  • 2
    If I understand correctly, your question isn't specific to Visual Studio and you generally want to create a hotkey that uses more than one non-modifier key. The syntax of your hotkey declaration is way off, that's why I recommend reading the respective part of the docs. To your problem: Combining more than one non-modifier keys in a hotkey isn't provided for in standard AHK, but there's a workaround that simulates this behavior pretty well, check it out here. – MCL Mar 5 '14 at 10:24
  • @MCL, thanks for the link! The loop solution indeed worked! But, I was determined to find a non-loop solution, and eventually found one (self-answered, below). Thank you for your thoughtful response, too. Actually, the {Ctrl Down}EC{Ctrl Up} wasn't a solution I tried in seriousness. I tried many other things (you can see some examples in my explanation), but I didn't want to explain everything I tried, and I also didn't want to scare off users who might help me, by looking like I already tried advanced techniques. I hope it's understandable... – Andrew Cheong Mar 6 '14 at 4:06
7

Finally figured it out.

tl;dr

Replace ^e with first desired keystroke. Replace 3 with ASCII index of second desired keystroke. This enforces that Ctrl is held through both keystrokes, else cancels.

~$Ctrl UP::
    ChordIsBroken := True
    Return
^e::
    ChordIsBroken := False
    Input, OutputVar, L1 M
    If (!ChordIsBroken && Asc(OutputVar) = 3)
    {
        MsgBox "Hello, World!"
    }
    Else
    {
        SendInput %OutputVar%
    }
    Return

To adapt this to Shift instead of Ctrl, you'd have to replace the Ctrls, remove the M, and do a simpler comparison like OutputVar = C instead of the Asc(OutputVar) = 3. Not sure how to extend this to Alt and Win but you might have to try L2 or something of the sort.

Explanation

Input seemed like an obvious place to begin. Input asks AHK to wait for user input, e.g.

^e::
    Input, OutputVar, L1        ; "L1" means wait for 1 keystroke then continue.
    If (OutputVar = "c")
    {
        MsgBox, "Hello, World!"
    }
    Return

The message box above triggers on CtrlE then C. But we're looking for CtrlC, so let's fix this:

^e::
    Input, OutputVar, L1 M      ; "M" allows storage of modified keystrokes (^c).
    If (Asc(OutputVar) = 3)     ; ASCII character 3 is ^c.
    {
        MsgBox "Hello, World!"
    }
    Return

Now we've got a message box on pressing CtrlE then CtrlC. But there's a problem with this: the message box triggers even when I release Ctrl between the two keystrokes. So, how do we detect essentially a {Ctrl Up} mid-input? You can't merely check on entrance—

^e::
    if (!GetKeyState("Ctrl"))
    {
        Return
    }
    Input, OutputVar, L1 M 
    ; ...

—nor can you merely check after input—

^e::
    Input, OutputVar, L1 M 
    if (!GetKeyState("Ctrl"))
    {
        Return
    }
    ; ...

—nor can you even get away with doing both, because no matter what, you'll miss the {Ctrl Up} while blocking for input.

Then I looked into documentation on Hotkey for inspiration. The custom combination operator, &, seemed promising. But unfortunately,

^e & ^c::
    ; ...

caused a compilation error; apparently the & is for combining unmodified keystrokes, only.

Finally, it was up to UP, and that's where I finally made the breakthrough. I redefined Ctrl's UP to set a toggle that would prevent the message box from triggering!

$Ctrl::Send {Ctrl Down}     ; The $ prevents an infinite loop. Although this
$Ctrl UP::                  ; line seems redundant, it is in fact necessary.
    ChordIsBroken := True   ; Without it, Ctrl becomes effectively disabled.
    Send {Ctrl Up}
    Return
^e::
    ChordIsBroken := False
    Input, OutputVar, L1 M
    If (!ChordIsBroken && Asc(OutputVar) = 3)
    {
        MsgBox "Hello, World!"
    }
    Return

Now, when I press CtrlE, then release the Ctrl, and press CtrlC, nothing happens, as expected!

There was one last thing to fix. On "cancellation" (a "broken chord"), I wanted all keystrokes to return to normal. But in the code above, the Input would have to "eat" a keystroke before returning, regardless a broken chord or an irrelevant secondary keystroke. Adding an Else case nicely resolves this:

    Else
    {
        SendInput %OutputVar%
    }

So, there you have it—"chords" in AutoHotkey. (Although, I wouldn't exactly call this a "chord." More like a melody, with a bass line ;-)


@hippibruder generously points out that I can avoid defining $Ctrl:: by using ~ to make $Ctrl UP:: non-blocking. This allows some simplification! (See tl;dr section at top for final result.)


One more thing. If, perchance, upon "cancellation" (a "broken chord"), you'd like to issue the first keystroke, i.e. CtrlE on its own, simply add that in the Else block,

    Else
    {
        SendInput ^e
        SendInput %OutputVar%
    }

and don't forget to change the hotkey to

$^e::

to avoid an infinite loop.

  • Do you mind posting an example or linking to an example for Ctrl + Shift + a or some specific key? I don't really understand your logic in the post. Thanks! – William Mar 25 '14 at 5:09
  • This is for the case where one would like to assign an action to, for example, Ctrl+k followed by Ctrl+a without releasing the Ctrl. For something like Ctrl+Shift+a of course the proper method would be simply ^+a::. – Andrew Cheong Mar 25 '14 at 5:11
  • Wow fast response. Well I'm trying to do the following with no luck AppsKey::, Ctrl & AppsKey:: and ^+AppsKey::. The 1st and 2nd works but not the 3rd – William Mar 25 '14 at 5:23
  • I think it's because AppsKey isn't technically a modifier key. Take a look at Is it possible to use AppsKey or other keys as modifier if you haven't already. Good luck! – Andrew Cheong Mar 25 '14 at 5:31
2

I don't think there is build-in support for key chords in AHK. One way to detect these would be to register a hotkey for the first key in the chord (^e) and then use the Input-Command to detect the next keys.

; Tested with AHK_L U64 v1.1.14.03 (and Visual Studio 2010)
; This doesn't block the input. To block it remove '~' from the hotkey and 'V' from Input-Cmd
~^e::
  ; Input-Cmd will capture the next keyinput as its printable representation. 
  ; (i.e. 'Shift-a' produces 'A'. 'a' produces 'a'. 'Ctrl-k' produces nothing printable. This can be changed with 'M' option. Maybe better approch; See help) 
  ; Only the next, because of 'L1'. (Quick-Fail; Not necessary)
  ; To detect 'c' and 'u' with control pressed I used them as EndKeys.
  ; If a EndKey is pressed the Input-Cmd will end and save the EndKey in 'ErrorLevel'.
  Input, _notInUse, V L1 T3, cu

  ; Return if Input-Cmd was not terminated by an EndKey
  ; or 'Control' is no longer pressed. (It would be better if Input-Cmd would be also terminated by a {Ctrl Up}. I don't know if that is possible)
  if ( InStr(ErrorLevel, "Endkey:") != 1
    || !GetKeyState("Control") )
    {
    return
    }

  ; Extract the EndKey-Name from 'ErrorLevel' (ErrorLevel == "Endkey:c")
  key := SubStr(ErrorLevel, 8)    
  if ( InStr(key, "c") == 1 )
    {
    TrayTip,, ^ec
    }
  else if ( InStr(key, "u") == 1 )
    {
    TrayTip,, ^eu
    }
  else
    {
    MsgBox, wut? key="%key%"
    }
return
  • Hi, @hippibruder. Thank you for your answer. Unfortunately, it behaved strangely on my AHK (and I'm running AHK_L, too), and I couldn't figure out why. If you could explain your code a little bit (the _, the ErrorLevel, etc.) I'd be willing to try again and see if you indeed have the superior solution that I should accept. – Andrew Cheong Mar 6 '14 at 4:08
  • 1
    @acheong87 I've added comments. Regarding your answer (can't add comment there): To get rid of "$Ctrl::Send {Ctrl Down}" add a '~' to "$Ctrl UP::" and remove "Send {Ctrl Up}". Also your code works perfect (i.e. like in VS) for me without the 'ChordIsBroken' variable all together (Only "^e" hotkey). – hippibruder Mar 6 '14 at 11:44
  • Cool, you're absolutely right about the ~! Thanks. I do need the ChordIsBroken though because otherwise I can release Ctrl between the two keystrokes, which I don't want to allow. I'm baffled by why your solution isn't working for me. I guess I'm running AHK_L v1.1.09.00 so maybe I should update? But for me currently, with your script, hitting Ctrl+E causes a line (in a text editor) to be deleted. – Andrew Cheong Mar 6 '14 at 13:28
  • I'm +1'ing you anyway because you definitely know what you're talking about, and helped me out with your comment. I'll upgrade my AHK later and report back if anything's changed. – Andrew Cheong Mar 6 '14 at 13:39
  • My code doesn't block the keystrokes. Maybe your editor deletes the current line with Ctrl+E. I just now realised that you don't want to allow a {Ctrl Up} at all. Mine imitates VS behaviour. ({Ctrl Down}e{Ctrl Up}{Ctrl Down}c{Ctrl Up} is legit) – hippibruder Mar 6 '14 at 13:51

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.