├── .gitignore ├── CHotkeyControl.ahk ├── README.md └── test.ahk /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.exe 3 | *.zip 4 | *.lnk 5 | *.scc 6 | *.png 7 | *.tmp.* 8 | Setup.ahk 9 | !Setup*.exe 10 | !*.Demo.exe 11 | *.bak 12 | -------------------------------------------------------------------------------- /CHotkeyControl.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | ToDo: 3 | 4 | * Shift + Numpad (with numlock on) does not work 5 | An up event is received for shift before down event. 6 | eg turn numlock on, hold Shift, Hit Numpad8 7 | Callback occurs for up event of shift before the callback occurs for the down event of NumPadUp 8 | +Numpad8 as an AHK hotkey never triggers, so this is not a valid combo. 9 | 10 | * Callback for pre-binding ? 11 | May need to tell hotkey handler to disable all hotkeys while in Bind Mode. 12 | 13 | * Callback after binding selected ? 14 | Hotkey controls may need to be able to ensure uniqueness. 15 | 16 | */ 17 | 18 | ; ----------------------------- Hotkey GuiControl class --------------------------- 19 | class _CHotkeyControl { 20 | static _MenuText := "Select new Binding|Toggle Wild (*) |Toggle PassThrough (~)|Remove Binding" 21 | 22 | __New(hwnd, name, callback, options := "", default := ""){ 23 | this._Value := default ; AHK Syntax of current binding, eg ~*^!a 24 | this.HotkeyString := "" ; AHK Syntax of current binding, eg ^!a WITHOUT modes such as * or ~ 25 | this.ModeString := "" 26 | this.HumanReadable := "" ; Human Readable version of current binding, eg CTRL + SHIFT + A 27 | this.Wild := 0 28 | this.PassThrough := 0 29 | this._ParentHwnd := hwnd 30 | this.Name := name 31 | this._callback := callback 32 | this._LastSC := -1 33 | this._LastEvent := -1 34 | 35 | ; Lookup table to accelerate finding which mouse button was pressed 36 | this._MouseLookup := {} 37 | this._MouseLookup[0x201] := { name: "LButton", event: 1 } 38 | this._MouseLookup[0x202] := { name: "LButton", event: 0 } 39 | this._MouseLookup[0x204] := { name: "RButton", event: 1 } 40 | this._MouseLookup[0x205] := { name: "RButton", event: 0 } 41 | this._MouseLookup[0x207] := { name: "MButton", event: 1 } 42 | this._MouseLookup[0x208] := { name: "MButton", event: 0 } 43 | 44 | ; Add the GuiControl 45 | Gui, % this._ParentHwnd ":Add", ComboBox, % "hwndhwnd AltSubmit " options, % this._MenuText 46 | this._hwnd := hwnd 47 | 48 | ; Find hwnd of EditBox that is a child of the ComboBox 49 | this._hEdit := DllCall("GetWindow","PTR",this._hwnd,"Uint",5) ;GW_CHILD = 5 50 | 51 | ; Bind an OnChange event 52 | fn := this.OptionSelected.Bind(this) 53 | GuiControl % "+g", % this._hwnd, % fn 54 | 55 | this.Value := this._Value ; trigger __Set meta-func to configure control 56 | } 57 | 58 | ; value was set 59 | __Set(aParam, aValue){ 60 | if (aParam = "value"){ 61 | this._ValueSet(aValue) 62 | return this._Value 63 | } 64 | } 65 | 66 | ; Read of value 67 | __Get(aParam){ 68 | if (aParam = "value"){ 69 | return this._Value 70 | } 71 | } 72 | 73 | ; Change hotkey AND modes to new values 74 | _ValueSet(hotkey_string){ 75 | arr := this._SplitModes(hotkey_string) 76 | this._SetModes(arr[1]) 77 | joy := StrSplit(arr[2], "Joy") 78 | if (joy[1] && joy[2]){ 79 | this._IsJoystick := 1 80 | } else { 81 | this._IsJoystick := 0 82 | } 83 | this.HotkeyString := arr[2] 84 | this._HotkeySet() 85 | } 86 | 87 | ; Change hotkey only and LEAVE modes 88 | _HotkeySet(){ 89 | this.HumanReadable := this._BuildHumanReadable(this.HotkeyString) 90 | this._value := this.ModeString this.HotkeyString 91 | this._UpdateGuiControl() 92 | ; Fire the OnChange callback 93 | this._callback.(this) 94 | 95 | } 96 | 97 | ; ============== HOTKEY MANAGEMENT ============ 98 | ; An option was selected in the drop-down list 99 | OptionSelected(){ 100 | GuiControlGet, option,, % this._hwnd 101 | GuiControl, Choose, % this._hwnd, 0 102 | if (option = 1){ 103 | ; Bind Mode 104 | ;ToolTip Bind MODE 105 | this._BindMode() 106 | 107 | } else if (option = 2){ 108 | ;ToolTip Wild Option Changed 109 | this.Wild := !this.Wild 110 | this.ModeString := this._BuildModes() 111 | this._HotkeySet() 112 | } else if (option = 3){ 113 | ;ToolTip PassThrough Option Changed 114 | this.PassThrough := !this.PassThrough 115 | this.ModeString := this._BuildModes() 116 | this._HotkeySet() 117 | } else if (option = 4){ 118 | ;ToolTip Remove Binding 119 | this.Value := "" 120 | } 121 | } 122 | 123 | ; Bind mode was enabled 124 | _BindMode(){ 125 | static WH_KEYBOARD_LL := 13, WH_MOUSE_LL := 14 126 | static modifier_symbols := {91: "#", 92: "#", 160: "+", 161: "+", 162: "^", 163: "^", 164: "!", 165: "!"} 127 | ;static modifier_lr_variants := {91: "<", 92: ">", 160: "<", 161: ">", 162: "<", 163: ">", 164: "<", 165: ">"} 128 | 129 | this._BindModeState := 1 130 | this._SelectedInput := [] 131 | this._ModifiersUsed := [] 132 | this._NonModifierCount := 0 133 | this._LastSC := -1 134 | this._LastEvent := -1 135 | 136 | Gui, new, hwndhPrompt -Border +AlwaysOnTop 137 | Gui, % hPrompt ":Add", Text, w300 h100 Center, BIND MODE`n`nPress the desired key combination.`n`nBinding ends when you release a key.`nPress Esc to exit. 138 | Gui, % hPrompt ":Show" 139 | 140 | ; Activate hooks 141 | ; ToDo: why does JHook not fire if hotkeys declared after hooks declared? 142 | fn := this._ProcessJHook.Bind(this) 143 | ; Activate joystick hotkeys 144 | Loop % 8 { 145 | joystr := A_Index "Joy" 146 | Loop % 32 { 147 | hotkey, % joystr A_Index, % fn 148 | hotkey, % joystr A_Index, On 149 | } 150 | } 151 | 152 | this._hHookKeybd := this._SetWindowsHookEx(WH_KEYBOARD_LL, RegisterCallback(this._ProcessKHook,"Fast",,&this)) ; fn) 153 | this._hHookMouse := this._SetWindowsHookEx(WH_MOUSE_LL, RegisterCallback(this._ProcessMHook,"Fast",,&this)) ; fn) 154 | 155 | ; Wait for Bind Mode to end 156 | Loop { 157 | if (this._BindModeState = 0){ 158 | break 159 | } 160 | Sleep 10 161 | } 162 | 163 | ; Bind mode ended, remove hooks 164 | this._UnhookWindowsHookEx(this._hHookKeybd) 165 | this._UnhookWindowsHookEx(this._hHookMouse) 166 | hotkey, IfWinActive 167 | Loop % 8 { 168 | joystr := A_Index "Joy" 169 | Loop % 32 { 170 | hotkey, % joystr A_Index, Off 171 | } 172 | } 173 | Gui, % hPrompt ":Destroy" 174 | 175 | out := "" 176 | end_modifier := 0 177 | 178 | if (this._SelectedInput.length() < 1){ 179 | return 180 | } 181 | 182 | ; Prefix with current modes 183 | hotkey_string := "" 184 | if (this.Wild){ 185 | hotkey_string .= "*" 186 | } 187 | if (this.PassThrough){ 188 | hotkey_string .= "~" 189 | } 190 | 191 | ; build hotkey string 192 | l := this._SelectedInput.length() 193 | Loop % l { 194 | if (this._SelectedInput[A_Index].Type = "k" && this._SelectedInput[A_Index].modifier && A_Index != l){ 195 | ; Convert keyboard modifiers from like LCtrl to ^ - do not do for last char as that is the "End Key" 196 | hotkey_string .= modifier_symbols[this._SelectedInput[A_Index].vk] 197 | } else { 198 | hotkey_string .= this._SelectedInput[A_Index].name 199 | } 200 | } 201 | 202 | ; trigger __Set meta-func to configure control 203 | this.Value := hotkey_string 204 | } 205 | 206 | ; Builds mode string from this.Wild and this.Passthrough 207 | _BuildModes(){ 208 | str := "" 209 | if (this.Wild){ 210 | str .= "*" 211 | } 212 | if (this.PassThrough){ 213 | str .= "~" 214 | } 215 | return str 216 | } 217 | ; Converts an AHK hotkey string (eg "^+a"), plus the state of WILD and PASSTHROUGH properties to Human Readable format (eg "(WP) CTRL+SHIFT+A") 218 | _BuildHumanReadable(hotkey_string){ 219 | static modifier_names := {"+": "Shift", "^": "Ctrl", "!": "Alt", "#": "Win"} 220 | 221 | dbg := "TRANSLATING: " hotkey_string " : " 222 | 223 | if (hotkey_string = ""){ 224 | return "(Select to Bind)" 225 | } 226 | 227 | JoyInfo := StrSplit(hotkey_string, "Joy") 228 | if (JoyInfo[1] != "" && JoyInfo[2] != ""){ 229 | return "Joy " JoyInfo[1] " Btn " JoyInfo[2] 230 | } 231 | 232 | str := "" 233 | mode_str := "" 234 | idx := 1 235 | ; Add mode indicators 236 | if (this.Wild){ 237 | mode_str .= "W" 238 | } 239 | if (this.PassThrough){ 240 | mode_str .= "P" 241 | } 242 | 243 | if (mode_str){ 244 | str := "(" mode_str ") " str 245 | } 246 | 247 | idx := 1 248 | ; Parse modifiers 249 | Loop % StrLen(hotkey_string) { 250 | chr := SubStr(hotkey_string, A_Index, 1) 251 | if (ObjHasKey(modifier_names, chr)){ 252 | str .= modifier_names[chr] " + " 253 | idx++ 254 | } else { 255 | break 256 | } 257 | } 258 | str .= SubStr(hotkey_string, idx) 259 | StringUpper, str, str 260 | 261 | ;OutputDebug % "BHR: " dbg hotkey_string 262 | return str 263 | } 264 | 265 | ; Splits a hotkey string (eg *~^a" into an array with 1st item modes (eg "*~") and 2nd item the rest of the hotkey (eg "^a") 266 | _SplitModes(hotkey_string){ 267 | mode_str := "" 268 | idx := 0 269 | Loop % StrLen(hotkey_string) { 270 | chr := SubStr(hotkey_string, A_Index, 1) 271 | if (chr = "*" || chr = "~"){ 272 | idx++ 273 | } else { 274 | break 275 | } 276 | } 277 | if (idx){ 278 | mode_str := SubStr(hotkey_string, 1, idx) 279 | } 280 | return [mode_str, SubStr(hotkey_string, idx + 1)] 281 | } 282 | 283 | ; Sets modes from a mode string (eg "*~") 284 | _SetModes(hotkey_string){ 285 | this.Wild := 0 286 | this.PassThrough := 0 287 | this.ModeString := "" 288 | Loop % StrLen(hotkey_string) { 289 | chr := SubStr(hotkey_string, A_Index, 1) 290 | if (chr = "*"){ 291 | this.Wild := 1 292 | } else if (chr = "~"){ 293 | this.PassThrough := 1 294 | } else { 295 | break 296 | } 297 | this.ModeString .= chr 298 | } 299 | } 300 | 301 | ; The binding changed - update the GuiControl 302 | _UpdateGuiControl(){ 303 | static EM_SETCUEBANNER:=0x1501 304 | DllCall("User32.dll\SendMessageW", "Ptr", this._hEdit, "Uint", EM_SETCUEBANNER, "Ptr", True, "WStr", modes this.HumanReadable) 305 | } 306 | 307 | ; ============= HOOK HANDLING ================= 308 | _SetWindowsHookEx(idHook, pfn){ 309 | Return DllCall("SetWindowsHookEx", "Ptr", idHook, "Ptr", pfn, "Uint", DllCall("GetModuleHandle", "Uint", 0, "Ptr"), "Uint", 0, "Ptr") 310 | } 311 | 312 | _UnhookWindowsHookEx(idHook){ 313 | Return DllCall("UnhookWindowsHookEx", "Ptr", idHook) 314 | } 315 | 316 | ; Process Keyboard messages from Hooks 317 | _ProcessKHook(wParam, lParam){ 318 | ; KBDLLHOOKSTRUCT structure: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx 319 | ; KeyboardProc function: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644984(v=vs.85).aspx 320 | 321 | ; ToDo: 322 | ; Use Repeat count, transition state bits from lParam to filter keys 323 | 324 | static WM_KEYDOWN := 0x100, WM_KEYUP := 0x101, WM_SYSKEYDOWN := 0x104 325 | ;static last_sc := -1, last_event := -1 326 | 327 | Critical 328 | 329 | if (this<0){ 330 | Return DllCall("CallNextHookEx", "Uint", Object(A_EventInfo)._hHookKeybd, "int", this, "Uint", wParam, "Uint", lParam) 331 | } 332 | this:=Object(A_EventInfo) 333 | 334 | vk := NumGet(lParam+0, "UInt") 335 | Extended := NumGet(lParam+0, 8, "UInt") & 1 336 | sc := (Extended<<8)|NumGet(lParam+0, 4, "UInt") 337 | sc := sc = 0x136 ? 0x36 : sc 338 | ;key:=GetKeyName(Format("vk{1:x}sc{2:x}", vk,sc)) 339 | key := GetKeyName(Format("sc{:x}", sc)) 340 | 341 | event := wParam = WM_SYSKEYDOWN || wParam = WM_KEYDOWN 342 | 343 | ;OutputDebug % "Processing Key Hook... " key " | event: " event " | WP: " wParam 344 | 345 | modifier := (vk >= 160 && vk <= 165) || (vk >= 91 && vk <= 93) 346 | obj := {Type: "k", name: key , vk : vk, event: event, modifier: modifier} 347 | 348 | ;OutputDebug, % "Key VK: " vk ", event: " event ", name: " GetKeyName(Format("vk{:x}", vk)) ", modifier: " modifier 349 | 350 | ; Decide whether to ignore input or not 351 | ; Ignored input is: 352 | ; Repeat down events 353 | ; Events for Control key that is not a L/R variant (ie Control, not LControl) as this is sent when RALT is pressed. 354 | if ( ! (sc = 541 || (this._LastEvent = event && this._LastSC = sc) ) ){ 355 | 356 | this._ProcessInput(obj) 357 | this._LastSC := sc 358 | this._LastEvent := event 359 | } 360 | 361 | return 1 ; block key 362 | } 363 | 364 | ; Process Mouse messages from Hooks 365 | _ProcessMHook(wParam, lParam){ 366 | /* 367 | typedef struct tagMSLLHOOKSTRUCT { 368 | POINT pt; 369 | DWORD mouseData; 370 | DWORD flags; 371 | DWORD time; 372 | ULONG_PTR dwExtraInfo; 373 | } 374 | */ 375 | ; MSLLHOOKSTRUCT structure: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644970(v=vs.85).aspx 376 | static WM_LBUTTONDOWN := 0x0201, WM_LBUTTONUP := 0x0202 , WM_RBUTTONDOWN := 0x0204, WM_RBUTTONUP := 0x0205, WM_MBUTTONDOWN := 0x0207, WM_MBUTTONUP := 0x0208, WM_MOUSEHWHEEL := 0x20E, WM_MOUSEWHEEL := 0x020A, WM_XBUTTONDOWN := 0x020B, WM_XBUTTONUP := 0x020C 377 | static button_map := {0x0201: 1, 0x0202: 1 , 0x0204: 2, 0x0205: 2, 0x0207: 3, 0x208: 3} 378 | static button_event := {0x0201: 1, 0x0202: 0 , 0x0204: 1, 0x0205: 0, 0x0207: 1, 0x208: 0} 379 | Critical 380 | if (this<0 || wParam = 0x200){ 381 | Return DllCall("CallNextHookEx", "Uint", Object(A_EventInfo)._hHookMouse, "int", this, "Uint", wParam, "Uint", lParam) 382 | } 383 | this:=Object(A_EventInfo) 384 | out := "Mouse: " wParam " " 385 | 386 | keyname := "" 387 | event := 0 388 | button := 0 389 | 390 | if (IsObject(this._MouseLookup[wParam])){ 391 | ; L / R / M buttons 392 | keyname := this._MouseLookup[wParam].name 393 | ;event := 1 394 | button := button_map[wParam] 395 | event := button_event[wParam] 396 | } else { 397 | ; Wheel / XButtons 398 | ; Find HiWord of mouseData from Struct 399 | mouseData := NumGet(lParam+0, 10, "Short") 400 | 401 | if (wParam = WM_MOUSEHWHEEL || wParam = WM_MOUSEWHEEL){ 402 | ; Mouse Wheel - mouseData indicate direction (up/down) 403 | event := 1 ; wheel has no up event, only down 404 | if (wParam = WM_MOUSEWHEEL){ 405 | keyname .= "Wheel" 406 | if (mouseData > 1){ 407 | keyname .= "Up" 408 | button := 6 409 | } else { 410 | keyname .= "Down" 411 | button := 7 412 | } 413 | } else { 414 | keyname .= "Wheel" 415 | if (mouseData < 1){ 416 | keyname .= "Left" 417 | button := 8 418 | } else { 419 | keyname .= "Right" 420 | button := 9 421 | } 422 | } 423 | } else if (wParam = WM_XBUTTONDOWN || wParam = WM_XBUTTONUP){ 424 | ; X Buttons - mouseData indicates Xbutton 1 or Xbutton2 425 | if (wParam = WM_XBUTTONDOWN){ 426 | event := 1 427 | } else { 428 | event := 0 429 | } 430 | keyname := "XButton" mouseData 431 | button := 3 + mouseData 432 | } 433 | } 434 | 435 | ;OutputDebug % "Mouse: " keyname ", event: " event 436 | this._ProcessInput({Type: "m", button: button, name: keyname, event: event}) 437 | if (wParam = WM_MOUSEHWHEEL || wParam = WM_MOUSEWHEEL){ 438 | ; Mouse wheel does not generate up event, simulate it. 439 | this._ProcessInput({Type: "m", button: button, name: keyname, event: 0}) 440 | } 441 | return 1 442 | } 443 | 444 | _ProcessJHook(){ 445 | this._ProcessInput({Type: "j", name: A_ThisHotkey, event: 1}) 446 | } 447 | 448 | ; All input (keyboard, mouse, joystick) should flow through here when in Bind Mode 449 | _ProcessInput(obj){ 450 | ;{Type: "k", name: keyname, code : keycode, event: event, modifier: modifier} 451 | ;{Type: "m", name: keyname, event: event} 452 | ; Do not process key if bind mode has been exited. 453 | ; Prevents users from being able to hit multiple keys together and exceeding valid length 454 | static modifier_variants := {91: 92, 92: 91, 160: 161, 161: 160, 162: 163, 163: 162, 164: 165, 165: 164} 455 | 456 | if (!this._BindModeState){ 457 | return 458 | } 459 | 460 | JoyUsed := 0 461 | modifier := 0 462 | out := "PROCESSINPUT: " 463 | if (obj.Type = "k"){ 464 | out .= "key = " obj.name ", code: " obj.vk 465 | if (obj.vk == 27){ 466 | ;Escape 467 | this._BindModeState := 0 468 | return 469 | } 470 | modifier := obj.modifier 471 | ; RALT sends CTRL, ALT continuously when held - ignore down events for already held modifiers 472 | Loop % this._ModifiersUsed.length(){ 473 | if (obj.event = 1 && obj.vk = this._ModifiersUsed[A_Index]){ 474 | ;OutputDebug % "IGNORING : " obj.vk 475 | return 476 | } 477 | ;OutputDebug % "ALLOWING : " obj.vk " - " this._ModifiersUsed.length() 478 | } 479 | this._ModifiersUsed.push(obj.vk) 480 | ; Push l/r variant to used list 481 | this._ModifiersUsed.push(modifier_variants[obj.vk]) 482 | } else if (obj.Type = "m"){ 483 | out .= "mouse = " obj.name 484 | } else if (obj.Type = "j"){ 485 | if (this._SelectedInput.length()){ 486 | ; joystick buttons can only be bound without other keys or modifiers 487 | SoundBeep, 500, 200 488 | return 489 | } 490 | this._BindModeState := 0 491 | } 492 | 493 | ; Detect if Bind Mode should end 494 | ;OutputDebug % out 495 | if (obj.event = 0){ 496 | ; key / button up 497 | if (!modifier){ 498 | this._NonModifierCount-- 499 | } 500 | if (this._InputCompare(obj, this._SelectedInput[this._SelectedInput.length()])){ 501 | this._BindModeState := 0 502 | } 503 | } else { 504 | ; key / button down 505 | if (!modifier){ 506 | if (this._NonModifierCount){ 507 | SoundBeep, 500, 200 508 | return 509 | } 510 | this._NonModifierCount++ 511 | } 512 | this._SelectedInput.push(obj) 513 | ; End if not modifier 514 | ;if (!modifier){ 515 | ; this._BindModeState := 0 516 | ;} 517 | } 518 | } 519 | 520 | ; Compares two Input objects (that came from hooks) 521 | _InputCompare(obj1, obj2){ 522 | if (obj1.Type = obj2.Type){ 523 | if (obj1.Type = "k"){ 524 | if (obj1.vk = obj2.vk && obj1.sc = obj2.sc){ 525 | return 1 526 | } 527 | } else if (obj1.Type = "m"){ 528 | return obj1.button = obj2.button 529 | } else if (obj1.Type = "j"){ 530 | 531 | } 532 | } 533 | return 0 534 | } 535 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHotkeyControl 2 | 3 | ## What? 4 | A `Hotkey` GuiControl for AutoHotkey. 5 | 6 | ## Why? 7 | Because the default AHK `Hotkey` GuiControl does not support many things, eg mouse input. 8 | 9 | ## How? 10 | The code makes use of `SetWindowsHookEx` DLL calls to hook the keyboard and mouse. 11 | 12 | ## About 13 | CHotkeyControl is a Class for AHK scripts that you can instantiate, which creates a GuiControl that can be used to select input (keyboard, mouse, joystick etc) that AHK supports (eg to pass to the `hotkey()` command). 14 | It consists of one ListBox - the current binding appears as the selected item, and the user can drop down the list to select various binding options such as Rebind, Toggle Wild Mode / Passthrough etc. 15 | 16 | ## Planned Features 17 | * Able to recognize any input that AHK could declare a hotkey for. 18 | Keyboard, mouse and joystick or any valid combination thereof. 19 | * Fires a callback whenever the user changes the binding. 20 | * Human readable hotkey description (eg `CTRL + ALT + LBUTTON`) 21 | * Supports Wild (*), PassThrough (~) etc. 22 | * Getter and Setters 23 | The `value` property of the class will hold the current AHK hotkey string (eg `^!LButton`) 24 | Setting value will change the hotkey 25 | 26 | **Note that CHotkeyControl does NOT actually bind hotkeys, It just replicates the functionality of the AHK Hotkey GuiControl** 27 | -------------------------------------------------------------------------------- /test.ahk: -------------------------------------------------------------------------------- 1 | #include CHotkeyControl.ahk 2 | 3 | ; ----------------------------- Test script --------------------------- 4 | #SingleInstance force 5 | OutputDebug DBGVIEWCLEAR 6 | test := new test() 7 | 8 | return 9 | 10 | GuiClose: 11 | ExitApp 12 | 13 | class test { 14 | __New(){ 15 | Gui, new, hwndhwnd 16 | this._hwnd := hwnd 17 | 18 | this.Hotkeys := {} 19 | 20 | callback := this.HotkeyChanged.Bind(this) 21 | this.MyHotkey := new _CHotkeyControl(hwnd, "MyHotkey", callback, "x5 y5 w200", "F12") 22 | this.MyHotkey.Value := "~+a" ; test setter 23 | Gui, Show, x0 y0 24 | } 25 | 26 | HotkeyChanged(hkobj){ 27 | ;MsgBox % "Hotkey :" hkobj.Name "`nNew Human Readable: " hkobj.HumanReadable "`nNew Hotkey String: " hkobj.Value 28 | ToolTip % hkobj.Value 29 | if (IsObject(this.Hotkeys[hkobj.name]) && this.Hotkeys[hkobj.name].binding){ 30 | ; hotkey already bound, un-bind first 31 | hotkey, % this.Hotkeys[hkobj.name].binding, Off 32 | hotkey, % this.Hotkeys[hkobj.name].binding " up", Off 33 | } 34 | ; Bind new hotkey 35 | this.Hotkeys[hkobj.name] := {binding: hkobj.Value} 36 | fn := this.HotkeyPressed.Bind(this, hkobj, 1) 37 | hotkey, % hkobj.Value, % fn 38 | hotkey, % hkobj.Value, On 39 | 40 | fn := this.HotkeyPressed.Bind(this, hkobj, 0) 41 | hotkey, % hkobj.Value " up", % fn 42 | hotkey, % hkobj.Value " up", On 43 | OutputDebug % "BINDING: " hkobj._Value 44 | } 45 | 46 | HotkeyPressed(hkobj, state){ 47 | if (state){ 48 | SoundBeep, 1000, 200 49 | } else { 50 | SoundBeep, 500, 200 51 | } 52 | } 53 | } 54 | 55 | --------------------------------------------------------------------------------