Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[AHK_H(+dll)] - SuperHotkey()


  • Please log in to reply
14 replies to this topic
HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
SuperHotkey() will make any amount of Hotkeys possible.
:!: Note this code is an example, real code looks different as you can see below.
^LButton & a & h::
!LButton & RButton & a::
^+a & h & k::
^+1 & 2 & 3 & 4::

Requires AutoHotkey_H28++

By launching several AutoHotkey threads and suspending its Hotkeys we can build up any Hotkey.
So for example ^!1 is a hotkey in main script which activates a hotkey in another thread, this hotkey again activates another set of hotkeys that contain actual code to run.


For SuperHotkey to work you will need [AHK_H] AutoHotkey.exe and AutoHotkey.dll H28+

- Save example code below as AutoHotkey.ahk
- Include AutoHotkey.dll and it's AutoHotkey.exe in same folder
- Double click AutoHotkey.exe.

SuperHotkey_Init(Script) will need to run before Superhotkey can be used.
Then your hotkey need to run SuperHotkey(Dll/Script Number) to activate another hotkey in a separate dll thread.
All required functions are also included in example script.


You can compile the script using AutoHotkeySC.bin included in AHK_H package
Compiled, internal AutoHotkey.dll will be used so you do not need to extract it :!:

Thanks Lexikos for FileExtract_ToMem

Press Ctrl & CtrlBreak to exit example script.
Following Hotkeys are used here:
^+1 & 2 & 3 & 4
^+a & h & k
^LButton & a & h
^LButton & b & d
^LButton & w & r
!LButton & RButton & a
!LButton & RButton & b
!LButton & RButton & c
;Comments = Launched by
Hotkey_1= ;^!1
(
[color=black]2 & 3::
SuperHotkey(2)
Return[/color]
)
Hotkey_2= ;Hotkey_1
(
[color=black]*4::
MsgBox 1234
Return[/color]
)
Hotkey_3= ;!LButton
(
[color=black]*RButton::
SuperHotkey(4)
Return[/color]
)
Hotkey_4= ;Hotkey_3
(
[color=black]*a::MsgBox AutoHotkey
*b::MsgBox Break
*c::MsgBox Control[/color]
)
Hotkey_5= ;^LButton
(
[color=black]a & h::MsgBox AutoHotkey
b & d::MsgBox by the way
w & r::MsgBox With Regards[/color]
)
Hotkey_6= ;^+a
(
[color=black]h & k::MsgBox AutoHotkey[/color]
)

Loop 6
	[color=red]SuperHotkey_Init(Hotkey_%A_Index%)[/color]

^+CtrlBreak::ExitApp
^+a::SuperHotkey(6)
^+1::SuperHotkey(1)
!LButton::SuperHotkey(3)
^LButton::SuperHotkey(5)

SuperHotkey_Init(Script){
	global
   static idx:=0
	idx++
   If !SH
		SH:=Object()
	SH[idx]:=AhkDllThread("AutoHotkey.dll")
	SH[idx].ahktextdll("#NoTrayIcon`nSH:=Object(" (&SH) ")`n" Script "`nSuspend:`nSuspend, Toggle`nReturn`n" CreateScript("SuperHotkey{}"))
   While !SH[idx].ahkReady()
		Sleep, 10
	SH[idx].ahkLabel("Suspend")
   Return idx
}

SuperHotkey(DllNr#){
   global
   SH[DllNr#].ahkLabel("Suspend")
   If RegExMatch(A_ThisHotkey, "(AppsKey|Left|Right|Up|Down|LWin|RWin|"
   . "LButton|RButton|MButton|Enter|BackSpace|Tab|Shift|Ctrl|"
   . "LCtrl|RCtrl|Alt|RAlt|LAlt|Space|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12)",Hotkey)
      KeyWait,%Hotkey1%
   else
      KeyWait,% SubStr(A_ThisHotkey,0)
   SH[DllNr#].ahkLabel("Suspend")
}

CreateScript(script){
  static mScript,FileExtract_ToMem:="FileExtract_ToMem"
  StringReplace,script,script,`n,`r`n,A
  StringReplace,script,script,`r`r,`r,A
  If RegExMatch(script,"m)^[^:]+:[^:]+|[a-zA-Z0-9#[email protected]]+\{}$"){
    If !(mScript){
      If (A_IsCompiled and IsFunc(FileExtract_ToMem)){
         pData:=0,DataSize:=0
        If (%FileExtract_ToMem%(">AUTOHOTKEY SCRIPT<", pData, DataSize) 
          || %FileExtract_ToMem%(">AHK WITH ICON<", pData, DataSize)){
          mScript:=StrGet(pData,"UTF-8")
          StringReplace,mScript,mScript,`n,`r`n,A
          StringReplace,mScript,mScript,`r`r,`r,A
          mScript .="`r`n"
        }
      } else {
        FileRead,mScript,%A_ScriptFullPath%
        StringReplace,mScript,mScript,`n,`r`n,A
        StringReplace,mScript,mScript,`r`r,`r,A
        mScript .= "`r`n"
        Loop,Parse,mScript,`n,`r
        {
          If A_Index=1
            mScript:=""
          If RegExMatch(A_LoopField,"i)^\s*#include"){
            temp:=RegExReplace(A_LoopField,"i)^\s*#include[\s+|,]")
            If InStr(temp,"%"){
              Loop,Parse,temp,`%
              {
                If (A_Index=1)
                  temp:=A_LoopField
                else if !Mod(A_Index,2)
                  _temp:=A_LoopField
                else {
                  _temp:=%_temp%
                  temp.=_temp A_LoopField
                  _temp:=""
                }
              }
            }
            FileRead,_temp,%temp%
            mScript.= _temp "`r`n"
          } else mScript.=A_LoopField "`r`n"
        }
      }
    }
    Loop,Parse,script,`n,`r
    {
      If A_Index=1
        script=
      else If A_LoopField=
        Continue
      If (RegExMatch(A_LoopField,"^[^:\s]+:[^:\s=]+$")){
        StringSplit,label,A_LoopField,:
        If (label0=2 and IsLabel(label1) and IsLabel(label2)){
          script .=SubStr(mScript
            , h:=InStr(mScript,"`r`n" label1 ":`r`n")
            , InStr(mScript,"`r`n" label2 ":`r`n")-h) . "`r`n"
        }
      } else if RegExMatch(A_LoopField,"^[^\{}\s]+\{}$"){
        StringTrimRight,label,A_LoopField,2
        script .= SubStr(mScript
          , h:=RegExMatch(mScript,"i)\n" label "\([^\)\n]*\)\n?\{")
          , RegExMatch(mScript,"\n\s*}\s*\K\n",1,h)-h) . "`r`n"
      } else
        script .= A_LoopField "`r`n"
    }
  }
  StringReplace,script,script,`r`n,`n,All
  Return Script
}

/*
    Function: FileExtract
 
    Extracts a file from this compiled script by using a dynamic FileInstall.
 
    Syntax:
        FileExtract( Source, Dest [, Flag ] )
 
    Parameters:
        Source  - The source string used in the original FileInstall.
        Dest    - The name of the file to be created.
        Flag    - Specify 1 to overwrite existing files, otherwise omit.
 
    Remarks:
        Unlike FileInstall, FileExtract() allows variables and expressions for Source,
        and does not cause Ahk2Exe to include a file into the executable.
*/
FileExtract(Source, Dest, Flag=0) {
    static init
    if !init
        cb := RegisterCallback("FileExtract_")
        ; cb->func->mJumpToLine->mActionType := ACT_FILEINSTALL
        , NumPut(SubStr(A_AhkVersion,12,2)+0>=59 ? 162 : A_AhkVersion>="1.0.48" ? 162:159, NumGet(NumGet(cb+28)+4), 0, "UChar") ; Fixed for AutoHotkey v1.0.48: ACT_FILEINSTALL has changed to 160.
        , DllCall("GlobalFree", "uint", cb)
    return FileExtract_(Source, Dest, Flag)
}
FileExtract_(Source, Dest, Flag) {
    FileCopy, %Source%, %Dest%, %Flag%
    return !ErrorLevel
}
 
/*
    Function: FileExtract_ToMem
 
    Extracts a FileInstall'd file into memory.
 
    Syntax:
        FileExtract_ToMem( Source, pData, DataSize [, InitialBufferSize, InitialBuffer ] )
 
    Parameters:
        Source       [in] - The source string used in the original FileInstall.
        pData    [in/out] - A pointer to the buffer where file data is written. See remarks.
        DataSize     [in] - If pData is zero, this indicates the initial buffer size.
                    [out] - Receives the number of bytes written to the buffer.
 
    Remarks:
        pData must specify zero or a valid pointer to memory allocated with GlobalAlloc().
 
        If the caller specifies a non-zero pData, it is used as the initial data buffer.
        If the buffer is too small, the function will reallocate it and update pData.
        The function does not delete the buffer on failure unless the caller specified zero.
 
        Once the data is no longer needed, free it using GlobalFree:
 
            DllCall("GlobalFree","uint",pData)
 
        DataSize indicates the amount of data written, not the size of the buffer.
        To determine the actual size of the buffer, use GlobalSize:
 
            MemSize := DllCall("GlobalSize","uint",pData)
*/
FileExtract_ToMem(Source, ByRef pData, ByRef DataSize)
{
    static ReadPipe, ConnectNamedPipe, ReadFile, GlobalReAlloc
    if !VarSetCapacity(ReadPipe)
    {
        ; Initialize the machine code function for reading from the pipe.
        hex =
        ( LTrim Join
        558BEC81EC0004000053568B75085733FF397E080F848D000000397E040F848400000057
        FF36FF561057BB00040000EB5E8B46088B4D088BD02B560C3BD1732803C08946088B560C
        2BC23BC1730503D18956086A02FF7608FF7604FF561885C074458B4D088946048B460C03
        460433FF85C976168D9500FCFFFF2BD08A0C0288088B4D0847403BF972F2014E0C6A008D
        450850538D8500FCFFFF50FF36FF561485C0758D40EB0233C05F5E5BC9C20400
        )
        ;~ MCode() - http://www.autohotkey.com/forum/viewtopic.php?t=21172
        VarSetCapacity(ReadPipe,StrLen(hex)//2)
        Loop % StrLen(hex)//2
           NumPut("0x" . SubStr(hex,2*A_Index-1,2), ReadPipe, A_Index-1, "Char")
        ;~ end
 
        ; Resolve ReadPipe()'s dependencies for later.
        hKernel32 := DllCall("GetModuleHandle", "str", "kernel32.dll")
        astr := A_IsUnicode ? "astr" : "str"
        ConnectNamedPipe := DllCall("GetProcAddress", "uint", hKernel32, astr, "ConnectNamedPipe")
        ReadFile         := DllCall("GetProcAddress", "uint", hKernel32, astr, "ReadFile")
        GlobalReAlloc    := DllCall("GetProcAddress", "uint", hKernel32, astr, "GlobalReAlloc")
    }
 
    UserOwnsData := !!pData ; True if pData is not [blank or zero].
    if !pData
    {   ; If DataSize is non-numeric or < 1, default to 1024.
        if (DataSize+0 < 1)
            DataSize := 1024
        pData := DllCall("GlobalAlloc","uint",0,"uint",DataSize)
    }
    else
    {   ; Get the actual size of the memory block,
        DataSize := DllCall("GlobalSize","uint",pData)
    }
 
    VarSetCapacity(ReadPipeStruct, 28, 0) ; ReadPipeStruct
 
    ; Fill ReadPipeStruct with ReadPipe()'s dependencies.
    NumPut(ConnectNamedPipe, ReadPipeStruct, 16)
    NumPut(ReadFile, ReadPipeStruct, 20)
    NumPut(GlobalReAlloc, ReadPipeStruct, 24)
 
    Random, pipe_name
 
    ; Create a named pipe (with an unpredictable name) for writing the file into.
    hNamedPipe := DllCall("CreateNamedPipe", "str", "\\.\pipe" pipe_name, "uint", 3
                    , "uint", 0, "uint", 255, "uint",0, "uint",0, "uint",0, "uint",0)
    ; Set the parameters for the pipe-reading thread.
    NumPut(hNamedPipe, ReadPipeStruct, 0)
    NumPut(pData, ReadPipeStruct, 4)
    NumPut(DataSize, ReadPipeStruct, 8)
 
    ; Create a thread to read from the pipe into memory.
    ; The thread will start immediately, but will wait for a pipe connection.
    hReadThread := DllCall("CreateThread", "uint", 0, "uint", 0, "uint", &ReadPipe
                            , "uint", &ReadPipeStruct, "uint", 0, "uint*", ReadThreadID)
 
    ; "Replace flag" *must* be specified since the pipe... exists.
    FileExtractResult := FileExtract(Source, "\\.\pipe" pipe_name, 1)
 
    if !FileExtractResult
    {   ; Open and close a connection to the pipe to terminate the thread.
        FileAppend,, \\.\pipe\%pipe_name%
    }
 
    Loop {
        ; Wait for the thread to terminate, or any window message to be received.
        r := DllCall("MsgWaitForMultipleObjectsEx", "uint", 1, "uint*", hReadThread
                                            , "uint", -1, "uint", 0x4FF, "uint", 0x6)
        if (r = 0) || (r = -1) ; WAIT_OBJECT_0 or WAIT_FAILED
            break
        Sleep, 1 ; Allow AutoHotkey to dispatch messages.
    }
 
    DllCall("DisconnectNamedPipe", "uint", hNamedPipe)
    DllCall("CloseHandle", "uint", hNamedPipe)
    DllCall("CloseHandle", "uint", hReadThread)
 
    if FileExtractResult || UserOwnsData
    {
        ; Either it was a success and we are returning the extracted data,
        ; or it failed and we are returning the memory to the caller, since
        ; they may want to reuse it.
        pData := NumGet(ReadPipeStruct,4)
        DataSize := NumGet(ReadPipeStruct,12)
    }
    else
    {
        ; If ReadPipe() fails because of low memory, pData may have been reallocated,
        ; so always use the value in the structure.
        DllCall("GlobalFree", "uint", NumGet(ReadPipeStruct,4))
        pData := DataSize := 0
    }
    return FileExtractResult
}


majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
You must take into account that most keyboards don't allow more then 3 keys to be pressed at the same time.
I noticed it because it was frustrating with some games when you have to jump, lean left and up and cast something. You typically get "beep" sound.
Posted Image

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

You must take into account that most keyboards don't allow more then 3 keys to be pressed at the same time.
I noticed it because it was frustrating with some games when you have to jump, lean left and up and cast something. You typically get "beep" sound.

Thanks for the note but I don't get why some hotkeys do that and some not :shock:
Do you think this is due to keyboard? I thought this is due to OS.
For example here I get only beep for ^+a & f & g, not for the others.
Even ^+asdf works fine but not ^+afg :shock:
Also when no script is running, pressing asdf is fine but not fgh
Is it the same for you?
SetBatchLines,-1
Hotkey_1=
(
f & h::MsgBox you pressed ^+afh
[color=red]f & g::MsgBox you pressed ^+afg[/color]
d & f::MsgBox you pressed ^+adf
s & d::SuperHotkey(2)
)
Hotkey_2=*f::Msgbox you pressed ^+asdf

SuperHotkey_Init("Hotkey_")
^+a::SuperHotkey(1)
Escape::ExitApp

Edit
As far as I understand all keys in the middle of keyboard have that problem "45tyghbn".
so using them in combination will produce beep.
Can anybody confirm?

Rapte_Of_Suzaku
  • Members
  • 901 posts
  • Last active: Jul 08 2011 02:12 PM
  • Joined: 29 Feb 2008
It is due to how keyboards are designed. 1 wire per key is inconvenient, so most keyboards are made with a grid pattern -- you figure out what key was pressed by which row+column shows activity. Thus holding e and f will usually make r impossible to detect. Since keyboards aren't perfectly square, there will be multiple grids and some of the grids will be warped (the e/f/r example I gave didn't have to work, it just happened to work for my keyboard). Modifier keys are specially designed to work in combinations.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

It is due to how keyboards are designed. 1 wire per key is inconvenient, so most keyboards are made with a grid pattern -- you figure out what key was pressed by which row+column shows activity. Thus holding e and f will usually make r impossible to detect. Since keyboards aren't perfectly square, there will be multiple grids and some of the grids will be warped (the e/f/r example I gave didn't have to work, it just happened to work for my keyboard). Modifier keys are specially designed to work in combinations.

This make sense, to work around this we need to press #e & f & r, than release #e and keep holding f & r, only then it will work.

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Code in first post was updated to use latest AutoHotkey.dll.
Now only one AutoHotkey.dll is required, compiled scripts can reuse included AutoHotkey.dll ;)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

Do you think this is due to keyboard? I thought this is due to OS.

All Rapte said, plus there are keyboards that don't have this limitation.
Posted Image

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

Do you think this is due to keyboard? I thought this is due to OS.

All Rapte said, plus there are keyboards that don't have this limitation.

Hm.., do you have an example of such a keyboard?
I will test it on my office keyboard.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
No office keyboard need that so they will not support it. Even high end office keyboards like for instance MS Natural Keyboard don't support that. You need gaming keyboard.

As always, gaming requires the best computers and peripherals.
For instance Logitech G15.

Thisthread contains related info (ignore 2nd answer, 3thd one is correct). It also mentiones some proggy named KeyScan to test it.
Posted Image

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
I see, thanks for info.

BTW. my logitech keyboard seems to be a little better/good enough, e.g. I can do D & F & G but I cannot do D & F & G & H :)

EDIT:
I have changed SuperHotkey_Init() to accept code instead of variable.
E.g. D & F & G Hotkey
SuperHotkey_Init("g::MsgBox 1")
d & f::SuperHotkey(1)


kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
it gives me following error
call to nonexistent function
-->054: SH:=Object()

HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008

it gives me following error
call to nonexistent function
-->054: SH:=Object()

you will need to use AHK_H as well as AutoHotkey.dll.

kenn
  • Members
  • 407 posts
  • Last active: Jan 14 2015 08:16 PM
  • Joined: 11 Oct 2010
Thanks for replying me HotKeyIt, I already used AHK_H, though I installed AHK_L. Sorry it was my noobness. I added #include AhkDllThread.ahk, it doesn't give any errors. I'm still trying it

automaticman
  • Members
  • 658 posts
  • Last active: Nov 20 2012 06:10 PM
  • Joined: 27 Oct 2006
Best software for testing your keyboard is AquaKeyTest.exe, you can google it. There is another strangeness, direct laptop keyboard and a ps2/usb attached external keyboard on the same laptop can behave differently.

Sam Bo
  • Members
  • 264 posts
  • Last active: Jun 13 2018 04:30 AM
  • Joined: 03 Dec 2011

I hope one day Autohotkey is able to do this as an inner feature.