Build hotkeys from an associative array?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
elebertus
Posts: 2
Joined: 29 Apr 2017, 03:37

Build hotkeys from an associative array?

29 Apr 2017, 03:56

Hey all!

So a little background on what I'm doing here. I'm running multiple Chrome Remote Desktop windows which happen to be running Everquest inside of them. I have a functional script that very easily maps one key to another statically. However I'd like to share this out with some friends and make it a bit more dynamic (using the ReadIni feature in the future as well - that isn't covered here though).

To get things started I'll paste my basic script here:

Code: Select all

; Description: Autohotkey script used for Chrome Remote Desktop
; and Everquest. It should be generally portable with any Chrome
; remote desktop app.
; VERSION: 1.0.1
; Author: @Elebertus


; This is used so we can match "Chrome Remote Desktop"
; in the `WinGetTitle` call.
SetTitleMatchMode RegEx 
; This can likely be disabled, but I've found that if
; you have another window over your screen its buggy.
DetectHiddenWindows, On
; My current CRD test is titled "eqbox1 - Chrome Remote Desktop"
; so keying off the generic "Chrome Remote Desktop"
; string allows flexibility. I'm not actually sure what
; happens if you have multiple windows..
WinGetTitle, crdWinTitle, Chrome Remote Desktop

; TODO: Registering the hotkey with the control object
; should be a function. I would like to be able to
; configure an array, or map of hotkeys and actions
; that we loop through and register as custom hotkeys.
; Otherwise you need to copy and paste this block for 
; each hotkey manually.

; The hotkey declaration. The "^" represents the ctrl key
; the syntax list is here: https://autohotkey.com/docs/Hotkeys.htm#Symbols
; e.g.: 
; !1:: == alt+1
; +1:: == shift+1
; ^1 & +2 == ctrl+1, shift+2
; the '<' and '>' brackets will indicate left/right meta keys 
; <+1:: == leftShif+1

^1::
; Check to make sure the window exists before sending
IfWinExist, %crdWinTitle%
{
    ; Introduces a delay for the key to be pressed and how long
    ; the key is pressed for. You may need to tinker with
    ; these values if you're seeing odd behavior.
    SetKeyDelay, 20, 20
    ; `Chrome_RenderWidgetHostHWND1` is the control object.
    ; It should be static. ControlFocus focuses input
    ; on the control object's window. I wasn't able to
    ; get CRD to work without this.
    ControlFocus,Chrome_RenderWidgetHostHWND1,%crdWinTitle%
    ; ControlSend: Control Object Name, The key to send, Title of window
    ; The only value you should need to change is the key to send
    ; or in this case "1". I believe you can even send
    ; complex things like /doability 1 and such. You
    ; will have to play with this.
    ControlSend,Chrome_RenderWidgetHostHWND1,1,%crdWinTitle%
}
return

; For easier copy-pasting:
; change YOUR_HOTKEY_TO_PRESS to the keybinding you want to use
; change YOUR_ACTION_FROM_HOTKEY_TO_PRESS to the action you want

;  YOUR_HOTKEY_TO_PRESS::
;  IfWinExist, %crdWinTitle%
;  {
;      SetKeyDelay, 20, 20
;      ControlFocus,Chrome_RenderWidgetHostHWND1,%crdWinTitle%
;      ControlSend,Chrome_RenderWidgetHostHWND1,YOUR_ACTION_FROM_HOTKEY_TO_PRESS,%crdWinTitle%
;  }

Now this works absolutely flawlessly. Realistically a user could copy and paste the block declaring an action for each hotkey and there really wouldn't be any problem. However I'd like to be able to first be able to declare an associative array, or map, that would be iterated through and have the hotkeys built form them. What I have so far here doesn't seem to be working.

First try:

Code: Select all

SetTitleMatchMode RegEx 
DetectHiddenWindows, On
WinGetTitle, crdWinTitle, Chrome Remote Desktop

; A configurable map of hotkeys you want
; to use. Example:
; EQHotKeys := {"^1": "^1"}
; which would map to ctrl+1 being sent to
; your CRD window.

EQHotKeys := {"^1": "1", "^2": "2", "^3": "3", "^4": "4"}


; Iterate the configured EQHotKeys map to
; build the hotkeys we want to use.
For keyPress, keySent in EQHotKeys
  buildHotKeys(keyPress, keySent)

; This function compiles the hotkey from
; the EQHotKeys map.
buildHotKeys(keyPress, keySent){
  HotKey, %keyPress%,EQHotKey

  ; Label for the hotkey declaration.
  ; This defines the action taken, or 
  ; the mapping we want for our hotkeys.
  EQHotKey:
    SetKeyDelay, 20, 20
    ControlFocus,Chrome_RenderWidgetHostHWND1,%crdWinTitle%
    ControlSend,Chrome_RenderWidgetHostHWND1,%keySent%,%crdWinTitle%
  }

  Return 
}
I've tried different places with returns and such but I feel like I'm missing something fundamental here. I suspect that the label inside the function is alright and the `HotKey` call is fine as well. I'm just not sure how this works with the flow of a for-loop. Maybe it's just a simple syntax issue I'm not following, or maybe it is something else.

My idea is to have either a data structure or configuration file that can be provided as input. The input is parsed and the hotkeys are registered in the script. This would make the script a bit more user friendly. If this is possible most of the script could be configurable and be pretty reusable in general too which would be fun.

Reading through the documentation it seems like I could maybe use `GoSub` inside of the function, or a subroutine, but I haven't had any luck with that.

Happy to provide anymore information or clarification. The documentation on the language spec is really pretty impressive. I was surprised how rich autothokey was. You can truly accomplish quite a lot with it. I feel like what I'm doing is probably a somewhat common pattern. It's very likely I'm stuck on syntax or something else quite simple.

Thanks for taking a look,

Elebertus
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: Build hotkeys from an associative array?

29 Apr 2017, 06:05

You cannot pass a parameter to a label, so you need functions.
In order to use the same function for all hotkeys, you need a "BoundFunc" object, like so:

Code: Select all

#SingleInstance force
SetTitleMatchMode RegEx 
DetectHiddenWindows, On
WinGetTitle, crdWinTitle, Chrome Remote Desktop
SetKeyDelay, 20, 20

EQHotKeys := {"^1": "1", "^2": "2", "^3": "3", "^4": "4"}

for key, mapping in EQHotkeys {
	fn := Func("SendKey").Bind(mapping)
	hotkey, % key, % fn
}

SendKey(keySent){
	global crdWinTitle
    ControlFocus,Chrome_RenderWidgetHostHWND1,%crdWinTitle%
    ControlSend,Chrome_RenderWidgetHostHWND1,%keySent%,%crdWinTitle%
}
elebertus
Posts: 2
Joined: 29 Apr 2017, 03:37

Re: Build hotkeys from an associative array?

29 Apr 2017, 11:23

@evilC

Ah okay. I'll have to read up on the BindFunc then. Your code works out of the box though.

This is a lot cleaner in general too. I'm sure after I read the docs it'll make sense as to why I need to be doing this.

Elebertus
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: Build hotkeys from an associative array?

29 Apr 2017, 11:42

It's simple - var := Func("MyFunc").Bind(1) gives you an object that calls the function MyFunc and passes it the parameter 1.
eg you can do %var%() or var.call() with that object and the function will be called and passed 1. The Func() bit makes an object that points to the function, so Func("MyFunc") would make an object that called MyFunc without any parameters - the Bind() bit adds a parameter to the object.
So we then take that and pass it as the parameter to the hotkey command, instead of a label: hotkey, F1, % var
You can pass any number of parameters, eg var := Func("MyFunc").Bind(1, "Hello", {a: b})

Note that you cannot do hotkey, F1, % Func("MyFunc").Bind(1) all on one line, even though the syntax is valid. You must build the boundfunc object on one line, then use it in the hotkey command on the next line. It's just a quirk of AHK.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], Holarctic, jameswrightesq and 421 guests