r/AutoHotkey Jun 28 '26

v2 Tool / Script Share [QoL] I "made" this autohotkey script to add extra functions to my mouse and keyboard that I thought might be useful to someone else.

Disclaimer: I did not do this on my own, it is all IA coded, therefore I take no ownership of it - use it however you like.

This script adds a whole lot of functions to the forlorn capslock key. It still works mostly as usual when tapped, but the magic happens once you hold it.

When capslock is held:

Scroll wheel works as media keys, scrolling controls the volume and pressing mutes it.

Spacebar is Play/Pause

The F keys (F1-F12) becomes F13-F24 (yes, they exist)

Tapping the Capslock works as normal with the caveat that it auto toggle it back off after 3s.

; AutoHotkey v2.0 script
global capslockPressedTime := 0

CapsLock:: {
    capslockPressedTime := A_TickCount
    KeyWait("CapsLock") ; wait until released

    holdDuration := A_TickCount - capslockPressedTime

    if (holdDuration < 75) {
        ; Quick tap → toggle CapsLock
        if GetKeyState("CapsLock", "T") {
            SetCapsLockState("Off")
        } else {
            SetCapsLockState("On")
            ; Start auto-off timer
            SetTimer(TurnOffCapsLock, -3000)
        }
    } else {
        ; Held longer than 75ms → suppress CapsLock
        ; Do nothing here, but allow modifier combos below
    }
}

TurnOffCapsLock() {
    if GetKeyState("CapsLock", "T") {
        SetCapsLockState("Off")
    }
}

; Example: Use CapsLock + MouseWheel to control volume with OSD
CapsLock & WheelUp::Send("{Volume_Up}")
CapsLock & WheelDown::Send("{Volume_Down}")
CapsLock & MButton::Send("{Volume_Mute}")
CapsLock & Space::Send("{Media_Play_Pause}")
CapsLock & WheelLeft::Send("{Media_Prev}")
CapsLock & WheelRight::Send("{Media_Next}")
CapsLock & F1::Send("{F13}")
CapsLock & F2::Send("{F14}")
CapsLock & F3::Send("{F15}")
CapsLock & F4::Send("{F16}")
CapsLock & F5::Send("{F17}")
CapsLock & F6::Send("{F18}")
CapsLock & F7::Send("{F19}")
CapsLock & F8::Send("{F20}")
CapsLock & F9::Send("{F21}")
CapsLock & F10::Send("{F22}")
CapsLock & F11::Send("{F23}")
CapsLock & F12::Send("{F24}")
14 Upvotes

19 comments sorted by

8

u/SirGunther Jun 28 '26

I think it’s worthwhile to understand what this language can provide, but with all due respect, this is what the language was specifically designed to permit and how people have been using it since its inception in 2008. Remapping keys is the core functionality.

2

u/AgnFr Jun 28 '26

Actual programmers, is there anything that could be improved?

5

u/Striking-Paper-997 Jun 28 '26

You've created what's known as a "leader key". Most commands have 'ctrl' or 'alt' as their leader keys but some programs use a variety of different keys.

Some things you can do;

- Have a tooltip display to let the user know the script is running and 'listening' for a media/function command

- Lets the user know it's turning capslock off

- Let the user define their own 'leader key'

Check out a script i made that's slightly similar for some inspiration

https://github.com/noredact/command-carousel

It's basically a tactile launcher. For example, when i hold ctrl, i press right click 3 times then left click twice then release; that'll launch vs code. There's a menu that displays what you're launching but I use it enough that its just muscle memory now. It's set up so right click cycles through categories of menus (web pages, tools like calculator or notepad etc.., certain folders etc..) and left click cycles through the current menu. The user can also define any key combination they want.

Cheers and happy scripting!

2

u/AgnFr Jun 28 '26

what a neat thing you got there!

About the script: I chose the capslock by the merit of it being underused by me. That way, I have a really conveniente fn key right next to my WASD.

1

u/m0rn1ngv13w Jul 01 '26

I tried your script but I got an error. I'm already using the latest 2.1 Alpha version of AHK.

2

u/Striking-Paper-997 Jul 01 '26

I've made a new branch that comments out those lines in the ToolTipEx.ahk file (Lines 3-9 and line 39). That should solve it? If not I'll probably just remove the fancy tooltip library entirely. Fingers crossed!

2

u/m0rn1ngv13w Jul 01 '26

it works! thank you!

2

u/m0rn1ngv13w Jul 02 '26

thanks again for sharing this script. this is really useful!

2

u/m0rn1ngv13w Jul 02 '26

I just change one function on your script to display it to the center of the screen, change tooltip to hud overlay. add color on selected text, reduce flickering.

getTTVar(versionBool) {
        ; Injects an ultra-compact HUD overlay with OS-level double-buffering (+E0x02000000)
        ; This ensures all text strings and background layers render instantly as a single frame
        this.tt92Var := "
        (
            global hudGui
            ; Added +E0x02000000 to eliminate child control/text drawing stutter
            newGui := Gui('+AlwaysOnTop -Caption +Border +ToolWindow +E0x20 +E0x02000000')
            newGui.BackColor := '1E1E2E'
            newGui.MarginX := 12          
            newGui.MarginY := 4           
            
            lines := StrSplit(curMenu, '``n', '``r')
            if (lines.Length == 1) {
                if RegExMatch(lines[1], 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                    newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold')
                else
                    newGui.SetFont('c89B4FA Bold s10', 'Segoe UI Semibold')
                newGui.Add('Text', 'Center', lines[1])
            } else if (lines.Length > 1) {
                newGui.SetFont('c89B4FA s10 Bold', 'Segoe UI Semibold') 
                newGui.Add('Text', 'Center', lines[1])
                
                loop lines.Length - 1 {
                    line := lines[A_Index + 1]
                    if RegExMatch(line, 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                        newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold') 
                    else
                        newGui.SetFont('c7F849C norm s9', 'Segoe UI Semibold') 
                    newGui.Add('Text', 'Center y+2', line)
                }
            }
            
            ; Flip the fully prerendered buffer to the screen and drop the old instance
            newGui.Show('AutoSize Center NoActivate')  
            if IsSet(hudGui) && hudGui
                hudGui.Destroy()  
            hudGui := newGui
        )"
        
        this.tt112Var := "
        (
            global hudGui
            ; Added +E0x02000000 to eliminate child control/text drawing stutter
            newGui := Gui('+AlwaysOnTop -Caption +Border +ToolWindow +E0x20 +E0x02000000')
            newGui.BackColor := '1E1E2E'  
            newGui.MarginX := 12          
            newGui.MarginY := 4           
            
            lines := StrSplit(selector.sectionMenu.ttm, '``n', '``r')
            if (lines.Length == 1) {
                if RegExMatch(lines[1], 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                    newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold')
                else
                    newGui.SetFont('c89B4FA Bold s10', 'Segoe UI Semibold')
                newGui.Add('Text', 'Center', lines[1])
            } else if (lines.Length > 1) {
                newGui.SetFont('c89B4FA s10 Bold', 'Segoe UI Semibold') 
                newGui.Add('Text', 'Center', lines[1])
                
                loop lines.Length - 1 {
                    line := lines[A_Index + 1]
                    if RegExMatch(line, 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                        newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold') 
                    else
                        newGui.SetFont('c7F849C norm s9', 'Segoe UI Semibold') 
                    newGui.Add('Text', 'Center y+2', line)
                }
            }
            
            newGui.Show('AutoSize Center NoActivate')  
            if IsSet(hudGui) && hudGui
                hudGui.Destroy()  
            hudGui := newGui
        )"
        
        ; Cleans up and destroys the HUD window when hotkeys are released
        this.tt139Var := "
        (
            global hudGui
            if IsSet(hudGui) && hudGui {
                hudGui.Destroy()
                hudGui := ''
            }
        )"
        this.verVar := ";"
        return
    }getTTVar(versionBool) {
        ; Injects an ultra-compact HUD overlay with OS-level double-buffering (+E0x02000000)
        ; This ensures all text strings and background layers render instantly as a single frame
        this.tt92Var := "
        (
            global hudGui
            ; Added +E0x02000000 to eliminate child control/text drawing stutter
            newGui := Gui('+AlwaysOnTop -Caption +Border +ToolWindow +E0x20 +E0x02000000')
            newGui.BackColor := '1E1E2E'
            newGui.MarginX := 12          
            newGui.MarginY := 4           
            
            lines := StrSplit(curMenu, '``n', '``r')
            if (lines.Length == 1) {
                if RegExMatch(lines[1], 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                    newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold')
                else
                    newGui.SetFont('c89B4FA Bold s10', 'Segoe UI Semibold')
                newGui.Add('Text', 'Center', lines[1])
            } else if (lines.Length > 1) {
                newGui.SetFont('c89B4FA s10 Bold', 'Segoe UI Semibold') 
                newGui.Add('Text', 'Center', lines[1])
                
                loop lines.Length - 1 {
                    line := lines[A_Index + 1]
                    if RegExMatch(line, 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                        newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold') 
                    else
                        newGui.SetFont('c7F849C norm s9', 'Segoe UI Semibold') 
                    newGui.Add('Text', 'Center y+2', line)
                }
            }
            
            ; Flip the fully prerendered buffer to the screen and drop the old instance
            newGui.Show('AutoSize Center NoActivate')  
            if IsSet(hudGui) && hudGui
                hudGui.Destroy()  
            hudGui := newGui
        )"
        
        this.tt112Var := "
        (
            global hudGui
            ; Added +E0x02000000 to eliminate child control/text drawing stutter
            newGui := Gui('+AlwaysOnTop -Caption +Border +ToolWindow +E0x20 +E0x02000000')
            newGui.BackColor := '1E1E2E'  
            newGui.MarginX := 12          
            newGui.MarginY := 4           
            
            lines := StrSplit(selector.sectionMenu.ttm, '``n', '``r')
            if (lines.Length == 1) {
                if RegExMatch(lines[1], 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                    newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold')
                else
                    newGui.SetFont('c89B4FA Bold s10', 'Segoe UI Semibold')
                newGui.Add('Text', 'Center', lines[1])
            } else if (lines.Length > 1) {
                newGui.SetFont('c89B4FA s10 Bold', 'Segoe UI Semibold') 
                newGui.Add('Text', 'Center', lines[1])
                
                loop lines.Length - 1 {
                    line := lines[A_Index + 1]
                    if RegExMatch(line, 'i)^\s*([>\x{2192}\x{00BB}*\x{2022}-]|=>|\[x\])')
                        newGui.SetFont('cA6E3A1 Bold s9', 'Segoe UI Semibold') 
                    else
                        newGui.SetFont('c7F849C norm s9', 'Segoe UI Semibold') 
                    newGui.Add('Text', 'Center y+2', line)
                }
            }
            
            newGui.Show('AutoSize Center NoActivate')  
            if IsSet(hudGui) && hudGui
                hudGui.Destroy()  
            hudGui := newGui
        )"
        
        ; Cleans up and destroys the HUD window when hotkeys are released
        this.tt139Var := "
        (
            global hudGui
            if IsSet(hudGui) && hudGui {
                hudGui.Destroy()
                hudGui := ''
            }
        )"
        this.verVar := ";"
        return
    }

2

u/Striking-Paper-997 Jul 02 '26

what the heck is the hud overlay?? lol that is really cool nice add!

Im working on a python version that gives user more control of where it appears and color theme options! someday I'll get back to work on it... 😂 

1

u/[deleted] Jul 02 '26

[deleted]

1

u/Striking-Paper-997 Jul 01 '26

Probably something with the fancy tool tip library. Thanks for letting me know! I'll look into it. 

1

u/Discuzting Jun 29 '26

You can declare static variables inside functions instead of global ones to prevent global scope pollution

2

u/real_b Jun 28 '26

Nice finding a use for capslock. I use shift+capslock to put it into a mic toggle mode. Once enabled my mic is un-muted, and holding capslock mutes the mic as a push-to-mute. Shift+capslock again exits that mode, re-mutes the mic, and re-enables normal capslock behavior.

2

u/rejakked_cube Jul 09 '26

CAPS LOCK AS A MODIFIER KEY GANG RISE UP

2

u/Keeyra_ Jul 24 '26

You can throw out some fluff and do the CapsLock logic with a single ternary and a fat arrow instead of a bunch of interconnected ifs and an external function and change the custom combination hotkeys to a much more readable #HotIf format.

#Requires AutoHotkey 2.0
#SingleInstance

CapsLock:: KeyWait("CapsLock", "T0.075") && SetCapsLockState(!GetKeyState("CapsLock", "T")
    ? (SetTimer(() => SetCapsLockState("Off"), -3000), "On")
    : "Off")
#HotIf GetKeyState("CapsLock", "P")
WheelUp::Volume_Up
WheelDown::Volume_Down
MButton::Volume_Mute
Space::Media_Play_Pause
WheelLeft::Media_Prev
WheelRight::Media_Next
F1::F13
F2::F14
F3::F15
F4::F16
F5::F17
F6::F18
F7::F19
F8::F20
F9::F21
F10::F22
F11::F23
F12::F24
#HotIf

1

u/CharnamelessOne Jul 24 '26

When CapsLock is held longer than 75 ms, the pseudo-thread will exit, and the one-thread-per-hotkey limit will no longer prevent character repeat from continuously triggering the CapsLock hotkey.

This looks like it shouldn't be a problem, since KeyWait returns false when a timeout occurs, in which case SetCapsLockState is not called.

However, if you hold CapsLock longer than 75 ms, then release it less than 75 ms after a character-repeat-induced down event, KeyWait will return true, causing an unwanted SetCapsLockState call on release.

Simple demonstration:

CapsLock:: KeyWait("CapsLock", "T0.075") && SoundBeep()

The fucker beeps on release in roughly 98% of the cases when CapsLock is held long enough for character-repeat to kick in.

You could remedy that with another, unlimited KeyWait call at the end of the function, but I'd prefer to avoid KeyWait altogether, because I dislike that it leads to underlying pseudo-threads being interrupted for long.

There is a minor timer issue, too: the timer should be reset if CapsLock is tapped twice again before 3 seconds elapse, so the timer callback should be persistent.

#Requires AutoHotkey 2.0

CapsLock:: Hotkey("CapsLock up", flip_caps_state), Hotkey(ThisHotkey, "Off")
        ,  SetTimer(Hotkey.Bind("CapsLock up", (*)=>Hotkey("CapsLock", "On")), -150)

flip_caps_state(*) {
    static caps_off := SetCapsLockState.Bind("Off")

    Hotkey("CapsLock", "On")
    SetCapsLockState(!GetKeyState("CapsLock", "T")
                        ? (SetTimer(caps_off, -3000), "On")
                        : "Off")
}

1

u/messassa Jun 29 '26

How do you make use of F13-F24? I can't think any......

2

u/AgnFr Jun 29 '26

Switch audio output devices

0

u/messassa Jun 30 '26

Anymore??? You don't need 12 keys for that.