WoW Auto Key Spammer with GUI (Customizable, Minimize/Restore, Persistent Settings)

Hey Everyone!

Sharing a polished AutoHotkey script I’ve been working on. Some input and fixes by @Leaske It’s designed to auto-repeat specific keys (R and F by default) with customizable intervals, transparency, and additional key support. I chose R and F because I didnt have much keybound to them and I try to use macro’s with ST/MT on different keys. This lets me still have some of my own keybinds too.

:white_check_mark: Includes:

  • Persistent GUI settings (saved between sessions in a .ini file)
  • Transparent, draggable always-on-top window
  • Minimize & restore functionality
  • Custom key support (add/remove easily)
  • Dynamic key repeat with modifier support (Ctrl/Alt/Shift)
  • In-Game only activation (works only when WoW window is active)

:arrow_down_small: Here’s the full script:

#Persistent
#SingleInstance Force
#NoEnv
SetWorkingDir %A_ScriptDir%
SetTimer, CheckWoW, 1000

global iniFile := "macro_settings.ini"
global sleepTime, transparency, macroPaused, winX, winY
global isDragging := false
global settingsVisible := false
global wowIsOpen := false
global keyList := "r,f" ; Comma-separated
global customKey := ""
global isMinimized := false

; Load saved settings
IniRead, sleepTime, %iniFile%, Settings, SleepTime, 150
IniRead, transparency, %iniFile%, Settings, Transparency, 200
IniRead, macroPaused, %iniFile%, Settings, MacroPaused, 0
IniRead, winX, %iniFile%, Settings, WinX, 100
IniRead, winY, %iniFile%, Settings, WinY, 100
IniRead, keyList, %iniFile%, Settings, KeyList, r,f

; GUI Setup
Gui, +AlwaysOnTop +ToolWindow -Caption +E0x80000
Gui, Margin, 10, 10
Gui, Color, 101010
Gui, Font, s10, Segoe UI

Gui, Add, Text, cLime w180 h20 gStartDrag, WoW Key Spammer [Drag me]
Gui, Add, Text, x+5 w20 h20 cYellow gMinimizeGui Center, _
Gui, Add, Text, x+5 w20 h20 cRed gCloseGui Center, X
Gui, Add, Text, xm y+10 cWhite, Repeat interval:
Gui, Add, Text, vSpeedDisplay x+5 w60 h20 Right cYellow, %sleepTime%ms
Gui, Add, Button, xm y+10 w95 h30 gToggleSettings, Settings
Gui, Add, Button, x+10 w95 h30 gTogglePause, % (macroPaused ? "Resume" : "Pause")

Gui, Font, s9
Gui, Add, GroupBox, vSettingsGroup w200 h150 Hidden, Settings
Gui, Add, Text, vSpeedLabel xp+10 yp+25 cWhite Hidden, Speed: %sleepTime%ms
Gui, Add, Slider, vSpeedSlider w180 Range50-300 AltSubmit gAdjustSpeed Hidden, %sleepTime%
Gui, Add, Text, vSpeedMin cGray x+0 Hidden, 50ms
Gui, Add, Text, vSpeedMax cGray xp-180 yp+15 Hidden, 300ms
Gui, Add, Text, vTransparencyLabel xp yp+25 cWhite Hidden, Transparency: %transparency%
Gui, Add, Slider, vTransparencySlider w180 Range25-255 AltSubmit gAdjustTransparency Hidden, %transparency%
Gui, Add, Text, vCustomKeyLabel xp yp+25 cWhite Hidden, Add custom key:
Gui, Add, Edit, vCustomKeyEdit w40 h20 Limit1 gSetCustomKey Hidden
Gui, Add, Button, vAddKeyButton x+5 w60 h20 gAddCustomKey Hidden, Add Key
Gui, Add, Button, vResetKeysButton x+5 w80 h20 gResetKeys Hidden, Reset to R/F
Gui, Add, Text, vMinViewLabel xm ym w200 h30 cLime Center Hidden, WoW Key Spammer (Click to restore)

Gui, Show, x%winX% y%winY% NoActivate, WoW Key Spammer
Gui, +LastFound
WinSet, Transparent, %transparency%

; Load hotkeys
Loop, Parse, keyList, `,
{
    key := A_LoopField
    AssignKeyHotkeys(key)
}
return

CheckWoW:
Process, Exist, Wow.exe
if (ErrorLevel != 0) {
    if (!wowIsOpen) {
        wowIsOpen := true
        if (!isMinimized)
            Gui, Show, NoActivate
    }
} else {
    if (wowIsOpen) {
        wowIsOpen := false
        Gui, Hide
    }
}
return

MinimizeGui:
if (!isMinimized) {
    isMinimized := true
    GuiControl, Hide, SpeedDisplay
    GuiControl, Hide, ToggleSettings
    GuiControl, Hide, TogglePause
    if (settingsVisible) {
        settingsVisible := false
        GuiControl, Hide, SettingsGroup
        GuiControl, Hide, SpeedLabel
        GuiControl, Hide, SpeedSlider
        GuiControl, Hide, SpeedMin
        GuiControl, Hide, SpeedMax
        GuiControl, Hide, TransparencyLabel
        GuiControl, Hide, TransparencySlider
        GuiControl, Hide, CustomKeyLabel
        GuiControl, Hide, CustomKeyEdit
        GuiControl, Hide, AddKeyButton
        GuiControl, Hide, ResetKeysButton
    }
    GuiControl, Show, MinViewLabel
    Gui, Show, w150 h30 NoActivate
}
else {
    Gosub, RestoreGui
}
return

RestoreGui:
isMinimized := false
GuiControl, Hide, MinViewLabel
GuiControl, Show, SpeedDisplay
GuiControl, Show, ToggleSettings
GuiControl, Show, TogglePause
Gui, Show, AutoSize NoActivate
return

CloseGui:
ExitApp
return

StartDrag:
if (isMinimized) {
    Gosub, RestoreGui
    return
}
PostMessage, 0xA1, 2
return

GuiSize:
WinGetPos, winX, winY
IniWrite, %winX%, %iniFile%, Settings, WinX
IniWrite, %winY%, %iniFile%, Settings, WinY
return

AdjustSpeed:
GuiControlGet, sleepTime,, SpeedSlider
GuiControl,, SpeedLabel, Speed: %sleepTime%ms
GuiControl,, SpeedDisplay, %sleepTime%ms
IniWrite, %sleepTime%, %iniFile%, Settings, SleepTime
return

AdjustTransparency:
GuiControlGet, transparency,, TransparencySlider
GuiControl,, TransparencyLabel, Transparency: %transparency%
WinSet, Transparent, %transparency%
IniWrite, %transparency%, %iniFile%, Settings, Transparency
return

ToggleSettings:
settingsVisible := !settingsVisible
GuiControl, % (settingsVisible ? "Show" : "Hide"), SettingsGroup
GuiControl, % (settingsVisible ? "Show" : "Hide"), SpeedLabel
GuiControl, % (settingsVisible ? "Show" : "Hide"), SpeedSlider
GuiControl, % (settingsVisible ? "Show" : "Hide"), SpeedMin
GuiControl, % (settingsVisible ? "Show" : "Hide"), SpeedMax
GuiControl, % (settingsVisible ? "Show" : "Hide"), TransparencyLabel
GuiControl, % (settingsVisible ? "Show" : "Hide"), TransparencySlider
GuiControl, % (settingsVisible ? "Show" : "Hide"), CustomKeyLabel
GuiControl, % (settingsVisible ? "Show" : "Hide"), CustomKeyEdit
GuiControl, % (settingsVisible ? "Show" : "Hide"), AddKeyButton
GuiControl, % (settingsVisible ? "Show" : "Hide"), ResetKeysButton
GuiControl,, ToggleSettings, % (settingsVisible ? "Close" : "Settings")
Gui, Show, AutoSize NoActivate
return

SetCustomKey:
GuiControlGet, customKey,, CustomKeyEdit
return

AddCustomKey:
if (RegExMatch(customKey, "^[a-zA-Z0-9]$") && !InStr(keyList, customKey)) {
    keyList .= "," customKey
    IniWrite, %keyList%, %iniFile%, Settings, KeyList
    AssignKeyHotkeys(customKey)
}
return

ResetKeys:
keyList := "r,f"
IniWrite, %keyList%, %iniFile%, Settings, KeyList
Reload
return

TogglePause:
macroPaused := !macroPaused
GuiControl,, TogglePause, % (macroPaused ? "Resume" : "Pause")
IniWrite, %macroPaused%, %iniFile%, Settings, MacroPaused
return

AssignKeyHotkeys(key) {
    Hotkey, IfWinActive, World of Warcraft
    Hotkey, $%key%, CustomKeyPressed
    Hotkey, $^%key%, CustomKeyPressed
    Hotkey, $+%key%, CustomKeyPressed
    Hotkey, $!%key%, CustomKeyPressed
    Hotkey, IfWinActive
}

CustomKeyPressed:
HandleKey(SubStr(A_ThisHotkey, InStr(A_ThisHotkey, "$") + 1))
return

#IfWinActive World of Warcraft

HandleKey(key) {
    if (macroPaused) {
        SendInput % key
        return
    }
    Loop {
        if !GetKeyState(key, "P")
            break
        modifiers := ""
        modifiers .= GetKeyState("Ctrl", "P") ? "^" : ""
        modifiers .= GetKeyState("Shift", "P") ? "+" : ""
        modifiers .= GetKeyState("Alt", "P") ? "!" : ""
        SendInput {Blind}%modifiers%%key%
        Sleep %sleepTime%
    }
}

; Default key handlers
$r::
$^r::
$+r::
$!r::
HandleKey("r")
return

$f::
$^f::
$+f::
$!f::
HandleKey("f")
return
3 Likes

ok, need a lil help if you dont mind. i made the script, but not sure how to get it to work in game. i press the buttons and nothing happens.

Running it and seems very good! Thanks for this and appreciate the hard work and @Leaske

1 Like

you need AHK installed. you run the script and you keybind GSE to F AND/OR R and go ham.

1 Like

Let me go through it step-by-step, i know you have already created the script. However I will start from the beginning.

  1. You create a new AHK script
    image

  2. You choose a name for your script (don’t matter)

  3. You open the script with an editor (I use notepad++)
    Then you simply add the code from the OP post to the script and save it:

  4. Once saved (remember the location) you simply click the script file you made
    image

  5. It should now pop-up as an overlay:

  6. Once this is setup and you see this overlay, you can then bind GSE to R or F or enter your own key. like so:

Now when you hold R it will run at the repeat interval of the script which you see in the overlay.

Hope that helps you and everyone else who is struggling.

2 Likes

i see where i got confused. been using same keys so long that i forgot to change my keybinds to the ones you set. lol ty for that reminder.

1 Like

Great explanation and screenshots - Good work.

1 Like

I’ve made an update to resolve an issue where the application would sometimes get “stuck” due to modifier keys being pressed. To troubleshoot, I added a way to display which modifier is currently active. The primary modifiers are “F” and “R”. It still saves location, speed, and transparency settings to a .ini file, and the window remains draggable by holding left-click on the “Drag” area. The minimize button also condenses the GUI to show only the essentials.


Screenshot 2025-03-24 162221

; AutoHotkey v2 Script - Compact GUI + Proper WoW Hotkey Context + No Blocking Outside WoW

global delay := IniRead("config.ini", "Settings", "Delay", 135)
global transparency := IniRead("config.ini", "Settings", "Transparency", 255)
global winX := IniRead("config.ini", "Settings", "PosX", "Center")
global winY := IniRead("config.ini", "Settings", "PosY", "Center")
global scriptActive := true
global minimized := false

; --- Create GUI ---
global guiObj := Gui("-Caption +AlwaysOnTop", "Script Control")
guiObj.BackColor := "0x202020"
guiObj.SetFont("s10 cWhite", "Segoe UI")

; --- "MS Speed:" Label ---
speedLabel := guiObj.Add("Text", "x10 y10 w75 h20", "MS Speed:")

; --- Speed Value ---
SpeedValueControl := guiObj.Add("Text", "x+5 yp w40 h20", delay " ms")

; --- STOP/START Button ---
ButtonControl := guiObj.Add("Button", "x+5 yp w55 h20", scriptActive ? "STOP" : "START")
ButtonControl.OnEvent("Click", ToggleButtonClicked)

; --- Minimize Button ---
minBtn := guiObj.Add("Button", "x+5 yp w25 h18", "_")
minBtn.OnEvent("Click", ToggleMinimize)

; --- Close Button ---
closeBtn := guiObj.Add("Button", "x+2 yp w25 h18", "X")
closeBtn.OnEvent("Click", GuiClose)

; --- Row 2: Controls (hidden when minimized) ---
SliderControl := guiObj.Add("Slider", "x10 y40 w230 h20 Range50-300 +AltSubmit", delay)
SliderControl.OnEvent("Change", SliderChanged)

transLabel := guiObj.Add("Text", "x10 y70 w100 h20", "Transparency:")
TransparencyDisplay := guiObj.Add("Text", "x+5 yp w50 h20", Round(transparency/255*100) " %")
TransparencySlider := guiObj.Add("Slider", "x10 y100 w230 h20 Range50-255 +AltSubmit", transparency)
TransparencySlider.OnEvent("Change", TransparencyChanged)

dragLabel := guiObj.Add("Text", "x10 y130 w270 h25 Border Center", "Current Key: None  |  Drag")
dragLabel.BackColor := "0x404040"
dragLabel.OnEvent("Click", DragWindow)
global CurrentKeyControl := dragLabel

; --- Show GUI (Load pos OR center) ---
if (winX = "Center" || winY = "Center") {
    CenterWindow(guiObj, 270, 170)
    guiObj.Show()
} else {
    guiObj.Show("w270 h170 x" winX " y" winY)
}
WinSetTransparent(transparency, guiObj.Hwnd)

; --- Close event saves position ---
guiObj.OnEvent("Close", GuiClose)

; --- Timer ---
SetTimer(UpdateCurrentKey, 100)

; --- GUI Functions ---
SliderChanged(*) {
    global delay
    delay := SliderControl.Value
    IniWrite(delay, "config.ini", "Settings", "Delay")
    SpeedValueControl.Text := delay " ms"
}

TransparencyChanged(*) {
    global transparency
    transparency := TransparencySlider.Value
    WinSetTransparent(transparency, guiObj.Hwnd)
    IniWrite(transparency, "config.ini", "Settings", "Transparency")
    TransparencyDisplay.Text := Round(transparency / 255 * 100) " %"
}

ToggleButtonClicked(*) {
    global scriptActive
    scriptActive := !scriptActive
    ButtonControl.Text := scriptActive ? "STOP" : "START"
}

UpdateCurrentKey(*) {
    global CurrentKeyControl
    local keys := []
    if GetKeyState("R", "P")
        keys.Push("R")
    if GetKeyState("F", "P")
        keys.Push("F")
    local mods := []
    if GetKeyState("LCtrl", "P")
        mods.Push("Ctrl")
    if GetKeyState("LShift", "P")
        mods.Push("Shift")
    if GetKeyState("LAlt", "P")
        mods.Push("Alt")
    local keyStr := (ObjCount(keys) ? JoinArray(keys, ", ") : "None")
    local modStr := (ObjCount(mods) ? " + " . JoinArray(mods, ", ") : "")
    CurrentKeyControl.Text := "Current Key: " . keyStr . modStr . "  |  Drag"
}

GuiClose(*) {
    SavePosition(guiObj)
    ExitApp()
}

ToggleMinimize(*) {
    global minimized
    minimized := !minimized
    if minimized {
        SliderControl.Visible := false
        transLabel.Visible := false
        TransparencySlider.Visible := false
        TransparencyDisplay.Visible := false
        dragLabel.Visible := false
        guiObj.Show("w265 h50")
    } else {
        SliderControl.Visible := true
        transLabel.Visible := true
        TransparencySlider.Visible := true
        TransparencyDisplay.Visible := true
        dragLabel.Visible := true
        guiObj.Show("w270 h170")
    }
}

DragWindow(*) {
    PostMessage(0xA1, 2,,, guiObj.Hwnd)  ; WM_NCLBUTTONDOWN
}

; --- Save & Center Helpers ---
SavePosition(gui) {
    WinGetPos(&x, &y,,, gui)
    IniWrite(x, "config.ini", "Settings", "PosX")
    IniWrite(y, "config.ini", "Settings", "PosY")
}

CenterWindow(gui, width, height) {
    screenW := SysGet(78)
    screenH := SysGet(79)
    x := (screenW - width) // 2
    y := (screenH - height) // 2
    gui.Move(x, y, width, height)
}

ObjCount(obj) {
    local count := 0
    for k, v in obj
        count++
    return count
}

JoinArray(arr, delim := ", ") {
    local result := ""
    local first := true
    for index, value in arr {
        if (!first)
            result .= delim
        result .= value
        first := false
    }
    return result
}

; --- HOTKEYS: Only Active In WoW ---
#HotIf WinActive("World of Warcraft")
$*R:: HoldR()
$*F:: HoldF()
#HotIf

HoldR() {
    while GetKeyState("R", "P") {
        if (scriptActive) {
            if GetKeyState("LCtrl", "P")
                SendInput("^r")
            else if GetKeyState("LShift", "P")
                SendInput("+r")
            else if GetKeyState("LAlt", "P")
                SendInput("!r")
            else
                SendInput("r")
        }
        Sleep(delay)
    }
}

HoldF() {
    while GetKeyState("F", "P") {
        if (scriptActive) {
            if GetKeyState("LCtrl", "P")
                SendInput("^f")
            else if GetKeyState("LShift", "P")
                SendInput("+f")
            else if GetKeyState("LAlt", "P")
                SendInput("!f")
            else
                SendInput("f")
        }
        Sleep(delay)
    }
}

4 Likes

Any way to make it a on off toggle, and to use Xbutton 1?

im using this program for wow, lol

works fine and all but every once in awhile it stop doing something

if your framilier with WoW rotation my toon just stand there every time its done it seem like

cant it refresh faster to attack always?like when its done with script just stands there for 5 sec to start over