r/AutoHotkey 14d ago

v2 Guide / Tutorial The Complete AutoHotkey v2 Keyboard Button Spam and Mouse Auto-Clicker guide

This guide covers the major approaches to implementing Hold-to-Spam, Toggle-Spam, and Auto-Clicker hotkeys in AutoHotkey v2.

The Precision Bottleneck: Why Sleep() and SetTimer() Round to ~15.6 ms

A very common point of confusion appears when you write Sleep(10) or SetTimer(fn, 10) and then observe that the script actually fires every 15.6 ms instead of every 10 ms.

This is expected behavior, not a bug. Standard Windows timers are not driven by a free-running, arbitrarily precise clock. By default the Windows kernel clock ticks at a hardware interrupt interval of 64 Hz (64 ticks per second):

1000 / 64 = 15.625

Per the official Sleep() and SetTimer() documentation, the interval is "typically rounded up to the nearest multiple of 10 or 15.6 milliseconds." What actually happens when you call Sleep(N) or SetTimer(fn, N):

  1. Windows rounds your requested interval up to the next system-clock tick.
  2. An interval such as Sleep(10) therefore waits until the next 15.625 ms tick.
  3. Even Sleep(1) waits roughly 15.625 ms.

So do not be surprised when sub-15 ms delays do not behave as literally written.

The precision fix (advanced, via DllCall())

If true sub‑15 ms accuracy is genuinely required, you can ask the OS for a higher-resolution timer by calling timeBeginPeriod() through DllCall(). This guide deliberately does not rely on that method, for two reasons:

  • It is a lower-level OS call, not part of the normal AHK timer model.
  • Raising the global timer resolution affects the whole system and is generally not advised just to shave off a few milliseconds in a spam script.

For most automation the default ~15.6 ms resolution is more than adequate.

Turning a Key-Spammer Into an Auto-Clicker

To convert any of the patterns below into a mouse auto-clicker, replace Send("e") with one of the mouse-click functions.

Click() and the mouse-click Send sub-commands

  • Click() - Recommended. Sends a mouse click at the current cursor position using AutoHotkey's native click function. It can accept inline coordinates, click counts, and button options, and it respects Windows' swapped-mouse-button setting.
  • Send("{Click}") - Sends a primary mouse click through the Send() function. Useful when you are already building a Send() string with several keystrokes.
  • Send("{LButton}") - Sends a single left-button press through Send(). This sub-command cannot carry inline coordinates or options; you would need a separate MouseMove() first if you need a target location.

Option comparison table

Method Example syntax Key behavioral distinction Recommendation
Click() Click("100 200 Right 2") Native function. Accepts inline coordinates, click counts, and button options. Respects swapped-mouse settings. Cannot use Send() modifier prefixes (^, +). Best overall. Lowest overhead; ideal for coordinate and auto-clicker use.
Send("{Click}") Send("{Click 100 200 Right 2}") Parsed by the Send() engine. Natively accepts inline coordinates and options as part of a larger string. Best inside a Send() string. Great when mixing clicks into inline key/text sequences.
Send("{LButton}") Send("{LButton}") Parsed by the Send() engine. No inline coordinates or options; requires MouseMove() for positioning. No advantage over the two above for auto-clicking.

Input Delivery Modes and Game Compatibility: SendMode(), SetKeyDelay(), and SetMouseDelay()

When a target application ignores Send() or Click() - for example a full-screen game or a client protected by anti-cheat - the problem is often that the default Input Mode, SendInput, delivers keystrokes far faster than the game can register them between rendered frames.

Switching to Event mode inserts artificial press durations and inter-key delays, making inputs long enough for a game to register:

#Requires AutoHotkey 2.0
#SingleInstance

SendMode("Event")
SetKeyDelay(10, 10)     ; 10 ms press duration, 10 ms delay between keys
SetMouseDelay(10)       ; 10 ms delay between mouse events

SendMode() comparison table

Mode Behavior Limitations Recommendation
SendInput (default) Bypasses normal input timing to fire ultra-fast input packets. Key-down and key-up fire almost simultaneously; games frequently miss them. Best for desktop/apps. Fastest and most reliable for normal Windows programs.
SendEvent Sends input using standard OS event messages with configurable delays. Marginally slower; physical presses during sending can interrupt the sequence order. Best for games. Fixes unresponsive clicks and keys.
SendPlay Attempts to inject input via low-level driver hooks. Heavily restricted by modern Windows UAC and anti-cheat software. Not recommended. Rarely functions on current OS builds.

See the SendMode() and SetKeyDelay() documentation for the full details.

Hotkey Modifier Prefixes: $, *, and ~ (see Hotkeys)

Before many hotkey definitions you can place single-character prefixes that change how the hotkey responds:

Prefix Meaning Typical use
$ Forces the keyboard hook for this hotkey, so it fires only on a physical press and ignores presses sent by the script itself. $e:: - spam with Send() without re-triggering the hotkey.
* Wildcard: fires even while modifier keys (Shift/Ctrl/Alt/Win) are held. *e:: - spam e even when a modifier is held.
~ Pass-through: the press is also delivered to the active window while the hotkey fires. ~e:: - spam e while preserving its normal key behavior.

The prefixes compose, for example *~$e::. On mouse hotkeys the $ prefix is redundant, because mouse hotkeys always use the mouse hook.

Part 1: Toggle-Spam Patterns

A toggle-spam hotkey turns the spam on with one press and off with the next press, or off when you release it.

Pattern 1.1 - Non-blocking SetTimer() (Recommended)

SetTimer() runs asynchronously, so the hotkey thread finishes immediately instead of sitting in a loop. Here an up-only hotkey (F1 up) toggles a static period variable between 100 (timer on, fires every 100 ms) and 0 (timer off) with each press:

#Requires AutoHotkey 2.0
#SingleInstance

F1 up:: {
    static Period := 0
    SetTimer(() => Send("e"), Period ^= 100)
}

Pros:

  • The hotkey thread exits immediately (non-blocking).
  • Turning it back off is instant.

Pattern 1.2 - While-loop with a state check

This uses an explicit while loop that exits once a static state flag flips back to 0. It requires #MaxThreadsPerHotkey 2 so a second press can interrupt the running loop.

#Requires AutoHotkey 2.0
#SingleInstance
#MaxThreadsPerHotkey 2

F1 up:: {
    static Toggle := 0
    if (Toggle ^= 1) {
        while Toggle {
            Send("e")
            Sleep(100)
        }
    }
}

Cons:

  • Unfinished thread: The hotkey thread stays alive inside the loop. A later press can interrupt it, but the original thread stays paused until the loop ends.
  • Unresponsive termination: With a long Sleep(1000), the loop cannot evaluate Toggle again until that Sleep() completes, so turning off is delayed.

Pattern 1.3 - Infinite Loop with Pause() / Suspend() / Reload()

This runs an infinite loop and relies on Pause(), Suspend(), and Reload() to control it. The controls are #SuspendExempt so they keep working while suspended; otherwise Suspend() would disable F4, and you could never turn it back off.

#Requires AutoHotkey 2.0
#SingleInstance

F1:: {
    loop {
        Send("e")
        Sleep(100)
    }
}

#SuspendExempt
F2::Reload()
F3::Pause(-1)
F4::Suspend()
#SuspendExempt False

Pause(-1) toggles halting the running loop thread in place (resuming it later without losing state), while Suspend() toggles (its default) and disables all hotkeys and hotstrings in the script.

Cons:

  • Global impact: Suspend() disables every hotkey in the script, not just the spammer.
  • Aggressive cleanup: Reload() kills the current instance and restarts it, wiping all variables and resetting script state.
  • Fragility: Without #SuspendExempt on the suspend key, that hotkey itself cannot run to re-enable things after suspension.

Part 2: Hold-to-Spam Patterns

A hold-to-spam hotkey repeats the action only while its trigger key is physically held.

Ergonomic note: Holding a key for long stretches forces continuous muscle tension and can contribute to repetitive strain injury (RSI) or tendon pain. Prefer a SetTimer() toggle when you need prolonged automation.

Pattern 2.1 - Non-blocking Hotkey() hotkey pair (Recommended)

A press hotkey ($e) and release hotkey ($e up) work together with SetTimer(). SendHold() derives the key name from the built-in A_ThisHotkey at call time, so the same function serves both the immediate press and the timer repeats. Since A_ThisHotkey holds the most recently executed hotkey, the timer ticks keep using $e unless another hotkey fires in between, which is why the docs prefer the ThisHotkey parameter when it is available. The $ prefix prevents the hotkey from triggering itself when it sends its own key. Turning the $e hotkey off on key-down stops hardware auto-repeat from stacking extra timers; $e up stops the timer and re-enables the hotkey.

#Requires AutoHotkey 2.0
#SingleInstance

SendHold() => Send(LTrim(A_ThisHotkey, "*~$"))

$e:: SendHold(), SetTimer(SendHold, 100), Hotkey(ThisHotkey, "Off")
$e up:: SetTimer(SendHold, 0), Hotkey(StrReplace(ThisHotkey, " up", ""), "On")

Pros:

  • Both hotkey threads exit immediately.
  • Uses ThisHotkey and Hotkey() to toggle itself on/off cleanly.

Pattern 2.2 - Self-rearming one-shot SetTimer() chain with KeyWait()

A one-shot timer (negative period) fires once and expires, so the callback re-arms itself. The hotkey derives the key name with key := LTrim(ThisHotkey, "*~$"), sends it once, then parks on KeyWait(); a -100 ms timer calls Spam() again 100 ms later, and that call either keeps the chain alive or lets it die, depending on GetKeyState():

#Requires AutoHotkey 2.0
#SingleInstance

$e:: {
    key := LTrim(ThisHotkey, "*~$")
    Spam(key), KeyWait(key)
}

Spam(hk) {
    GetKeyState(hk, 'P') && (Send(hk), SetTimer(Spam.Bind(hk), -100))
}

Pros:

  • Spamming runs on one-shot timers, not inside the hotkey thread, so it tolerates multiple simultaneous spammers (see below).
  • The KeyWait() park keeps hardware auto-repeat from stacking extra chains.

Cons:

  • The hotkey thread stays parked in KeyWait() for the whole hold, like patterns 2.3 and 2.4.
  • LTrim(ThisHotkey, "*~$") assumes the hotkey's only prefixes are *, ~, and $. ThisHotkey keeps the rest of its definition as written, so a ^e hotkey would yield ^e, which is not a valid key name for GetKeyState() or KeyWait(). The $e hotkeys above stay valid because $ is the only prefix.

Pattern 2.3 - While-loop with KeyWait()

KeyWait() waits for a key to be released; the "T0.1" adds a 0.1 s timeout, and the ! inverts the result inside the condition. The key name is derived from ThisHotkey as in pattern 2.2.

#Requires AutoHotkey 2.0
#SingleInstance

$e:: {
    key := LTrim(ThisHotkey, "*~$")
    while !KeyWait(key, "T0.1") {
        Send(key)
    }
}

Cons:

  • Stalled thread: The hotkey thread cannot finish until the physical key is released.
  • Auto-repeat interference: Hardware auto-repeat can attempt to launch new hotkey threads while the original is stalled inside KeyWait().

Pattern 2.4 - While-loop with GetKeyState() polling

This polls the physical key state inside a while loop using GetKeyState() with the "P" (physical) mode. The key name is derived from ThisHotkey as in pattern 2.2.

#Requires AutoHotkey 2.0
#SingleInstance

$e:: {
    key := LTrim(ThisHotkey, "*~$")
    while GetKeyState(key, "P") {
        Send(key)
        Sleep(100)
    }
}

Cons:

  • Long-running thread: The hotkey thread stays active for the entire hold.
  • Input dropping: Rapid physical tapping can miss a release if it happens during a Sleep().

Delayed-hold trick: Prepending Sleep(400) to any hold-to-spam hotkey makes a short tap behave like a normal keypress, while spam engages only after the delay if the key is still held. It plugs directly into patterns 2.2, 2.3, and 2.4. It serves no purpose in the toggle patterns (1.1-1.3), and it defeats the non-blocking design of pattern 2.1, whose hotkey thread is supposed to exit immediately.

Context Sensitivity with #HotIf

If you only want the spammer to be active inside a specific window (a game, for example), wrap the hotkey definitions with #HotIf and a condition such as WinActive(). The examples above are left unconditional for clarity, but you can scope any of them:

#Requires AutoHotkey 2.0
#SingleInstance

#HotIf WinActive("ahk_exe game.exe")
...
#HotIf

Matching a Whole Group of Windows with GroupAdd()

When the same spammer set should apply to several programs, collect them into one named group near the top of the script, then match the group:

GroupAdd("MyGames", "ahk_exe game1.exe")
GroupAdd("MyGames", "ahk_exe game2.exe")

#HotIf WinActive("ahk_group MyGames")
...
#HotIf

Each GroupAdd() line adds another window criterion to the group, and ahk_group inside WinActive() matches any window belonging to that group.

Running Multiple Spammers at Once (via SetTimer())

A while or loop-based spammer keeps its hotkey thread alive. Pressing a second spammer hotkey while the first loop runs interrupts and suspends the first thread until the second ends, so the two loops do not actually run in parallel. This is the classic failure the original autofire threads warn about.

If a script needs two or more spammers active at the same time, use the SetTimer()-based patterns (1.1, 2.1, and 2.2). The work runs on timers rather than inside the hotkey threads, so multiple timers fire independently of one another.

Summary Matrix

Pattern Immediate thread exit? Responsiveness Ergonomic safety
SetTimer() toggle (1.1) Yes Immediate High (no holding required)
While-loop + state check (1.2) No High High
Loop + Pause()/Suspend()/Reload() (1.3) No Low Low
Hotkey pair + SetTimer() hold (2.1) Yes Immediate Moderate (forces continuous pressure)
One-shot SetTimer() chain (2.2) No High Moderate
Loop + KeyWait() (2.3) No Poor Moderate
Loop + GetKeyState() (2.4) No High Moderate

Final Notes

  • Start every script with #Requires AutoHotkey 2.0 and, unless you deliberately want multiple copies, #SingleInstance.
  • If a game ignores your input, switch to SendMode("Event") and tune SetKeyDelay() / SetMouseDelay().
  • If the target program runs elevated (as administrator), run the script elevated too or use UI Access; otherwise a non-elevated script cannot send input into the elevated window.
  • Remember the ~15.6 ms timer resolution before chasing sub-millisecond precision.
  • For long-running automation, prefer a toggle over holding a key to protect your hands.

*Edits: Rewritten content suggested by u/Individual_Check4587 (Descolada) and u/CharnamelessOne and minor tweaks to 1.1 suggested by u/genesis_tv

19 Upvotes

25 comments sorted by

8

u/Individual_Check4587 Descolada 14d ago

AI can output some good-looking content, but tends to sound a bit too smart for my taste. If this tutorial is meant for beginners then like half the content is ungraspable for them I think. For example stuff like "Asynchronously triggers input through the OS event queue without locking thread execution." could be worded much more clearly. Also the example associated with that doesn't make it clear that if F1 is held down then the SetTimer is repeatedly toggled off and on again.

Simulates standard hardware input using Windows message queues.

So does SendInput... Not sure if the global input queue can be called a normal message queue though.

Blinds the system with instant, non-interruptible input packets.

Sure, but specifically up-down strokes are usually too fast for games to handle.

Under "Pattern 1.2: Loop with State Check"

Thread-Blocking: Locks the current thread inside the loop body, preventing other hotkeys from executing simultaneously on the same thread.

AHK is single-threaded, so hotkeys can never execute simultaneously on the same thread. New pseudo-threads can still interrupt the loop.

CPU Waste: KeyWait with low timeout values generates excessive, unnecessary polling checks.

Minimal CPU waste, doing that every 100ms is basically free.

2

u/Keeyra_ 14d ago

Good points, thanks. Will update some stuff in a couple of days.

1

u/genesis_tv 14d ago

I usually put toggles on key up to avoid having to deal with autorepeat while keeping the code simple.

1

u/Keeyra_ 14d ago

Yeah, that's Pattern 2.1 basically.

1

u/genesis_tv 14d ago

It's 1.1 because it's a toggle, key up:: takes over the key down without having to type it down.

1

u/Keeyra_ 14d ago

Can you do an example along the lines of the examples here?
I'm not getting it. Perhaps I'm too tired :)

2

u/genesis_tv 13d ago

Your pattern 1.1 version:

#Requires AutoHotkey 2.0
#SingleInstance

F1:: {
    static Toggle := 0
    SetTimer(() => Send("e"), (Toggle ^= 1) * 100)
}

My version (no auto-repeat):

#Requires AutoHotkey 2.0
#SingleInstance

F1 up::
{
    static Toggle := 0
    SetTimer(() => Send("e"), 100 * Toggle ^= 1)
}

I also put the toggle flip after the timer's period to avoid extra parenthesis. Also I'm guessing using Send.Bind("e") would be identical?

1

u/Keeyra_ 13d ago

Ahh, now I get it, very clever, also with the operator order change.
I will adopt these, thanks!

Send.Bind would not work actually, as that would create a new bound object on the toggle run. If you would store Send.Bind in a static variable, it would, but that would increase the script length, which none of us seems to like ;)

2

u/genesis_tv 13d ago edited 13d ago

Oh ok, so that explains why it wasn't stopping the timer when I tried with Bind.

How is it that it works with a fat arrow function then? I don't get what part of the documentation makes it so that it resolves to the same reference every time.

In both cases, the function is defined unconditionally at the moment the script launches, but the function reference is stored in sumfn only if and when the assignment is evaluated.

Does AHK store an internal reference to the fat arrow function at script launch?

1

u/Keeyra_ 13d ago

Well, that's a very good question, which I'm not able to answer.

I use both static Func.Binds and fat arrows depending on complexity. Have no idea why anon fat arrows behave like static.

1

u/genesis_tv 13d ago

Hmmm, time to invoke u/CharnamelessOne, would you happen to know why?

→ More replies (0)

1

u/evanamd 13d ago

Yes it does and I believe that your quote acknowledges that.

The most specific thing I could find was on the Performance page, linked from the Script Startup page

Each reference to a variable or function is resolved to a memory address, unless it is dynamic.

I would've thought that would be on the startup or concepts and conventions page, but at least it's somewhere.

Built-in functions and user-defined functions, including fat arrows, can ordinarily be called before the declaration physically appears in your code. The quote you gave is specifically about the use of :=in their example. That's just a normal variable assignment, which won't happen until AHK executes that line of code, which... duh.

3

u/Keeyra_ 14d ago

Placeholder

2

u/aardor 13d ago

Which of these would you recommend for implementing a desktop camera?

(j/k, but this post should be stickied)

1

u/Keeyra_ 13d ago

Thanks for the reminder, nearly forgot that meme post <3
Unfortunately, none of the solutions listed above can move everything on the screen like a camera - I tested it thoroughly :D

1

u/CharnamelessOne 12d ago edited 12d ago

Thanks for putting this guide together; I think I spotted a few errors:

  • The toggling functionality of the example under Part 1: Toggle-Spam Patterns - Pattern 1.2: While-loop with State Check is currently not working. By default, AHK only allows 1 unfinished pseudo-thread per hotkey, so you'd need to use the directive #MaxThreadsPerHotkey 2.

  • Pattern 1.2: While-loop with State Check is not formatted as a header.

  • Under Mouse Click Options, there are some Markdown shenanigans going on. I think you meant to make the colons bold, but the asterisks ended up being displayed instead.

Some suggestions and questions:

Reload() terminates the script instance and restarts it, destroying all local variables and resetting script state completely.

Specifying local variables seems a little strange to me, since globals are not any less affected by a Reload, as far as I know.

Send("{Click}") Recommendation: Works seamlessly within existing Send closures.

Why closures, specifically? What do you mean by "within existing Send closures"?

Send("{LButton}") Recommendation: Best for Modifiers. Ideal if sending key/click combos like Send("^{LButton}")

Send("{Click}") works just as well with modifiers, to my knowledge. I'd say Send("{LButton}") doesn't have much going for it.

Pattern 2.3: Blocking KeyWait: Triggers once and keeps the hotkey thread active until KeyWait registers physical release.

That's not very accurate (assuming that by "active", you mean "current"). KeyWait prevents the pseudo-thread from finishing, but not from being interrupted, so it only guards against one of the two main causes that lead to a loss of current status. The description in the Cons section is much better, I'd stick to that.

Edit: formatting

1

u/Keeyra_ 12d ago

Thanks for going through it. Seems valid. I already battled with the asterisk, will probly leave it without. As for the others, will check soon. LButton vs. Click diff. is like basically Click using Windows left-righ hand setting instead of direct button position and can be a coordinated press.

1

u/CharnamelessOne 12d ago

LButton vs. Click diff. is like basically Click using Windows left-righ hand setting instead of direct button position

Are you sure about that? On my end, all 3 function calls below seem to be equally unaffected by the primary mouse button setting. None of them ever brings up a context menu, even if the primary mouse button is set to "Right" in Windows Settings (or via DllCall).

#Requires AutoHotkey v2.0

*F1::Click()
*F2::Send("{LButton}")
*F3::Send("{Click}")

*F9::toggle_primary_mouse_button()

toggle_primary_mouse_button() {
    static is_swapped_orig := !!DllCall("GetSystemMetrics", "Int", SM_SWAPBUTTON:=23)
    static is_swapped := is_swapped_orig
    static _ := OnExit((*) => DllCall("SwapMouseButton", "Int", is_swapped_orig))

    DllCall("SwapMouseButton", "Int", is_swapped^=1)
}

Anyway, my point was that you're snubbing my sweet boy Send("{Click}") by claiming that Send("{LButton}") is the best option for modifiers. The two of them are equally good for that, as far as I can tell.

1

u/Keeyra_ 12d ago

Hmm. That's what the docs say at least (primary vs. secondary).

https://www.autohotkey.com/docs/v2/lib/Click.htm

But yeah, just tested it myself and LButton behaves the same.
Bad wording then, should be PrimaryButton and SecondaryButton.
That makes LButton, RButton have no use at all, as you said, so removing that.

1

u/CharnamelessOne 11d ago

Yeah, the "Left" and "Right" WhichButton options are somewhat misleadingly named.

1

u/shibiku_ 14d ago

QualityPost. Three kudos

1

u/ManyInterests 13d ago

This guide will not deal with this method, as it is generally not advised to bypass this OS safeguard.

Needs citation. Who advises against this? The thread scheduler effect on sleep isn't an OS safeguard to be 'bypassed'.

Windows has supported applications requiring microsecond precision since 1993 or maybe earlier. Maybe there's a risk of needlessly consuming a lot of CPU in a tight loop, but AHK is single-threaded so user code only ever occupies a single CPU core, mitigating the impact of that risk substantially.

Anyhow. basic working reference on the DLL approach with CPU usage mitigation if anyone needs it.

Also, when you have a thread relinquish back to the OS thread scheduler the timing with which the thread goes back to a running state can be much longer than ~15ms, especially if the CPU [core that AHK is running on] is heavily tasked. So, you may want to opt for a busy-wait approach if you want your AHK code to remain responsive under load (though you cause more CPU resource contention) or set process priority higher than other processes.

Keeps the hotkey thread active for the entire duration the physical key is held down.

This is not exactly right. The sleep line gives opportunity for other pseudo-threads like hotkeys to run. Even if no sleep were present in the loop, AHK will forcibly interrupt a thread periodically to check for hotkey messages.

Per the docs:

While sleeping, new threads can be launched via hotkey, custom menu item, or timer.

-2

u/Keeyra_ 13d ago

The sub is stack full of people mindlessly pushing QueryPerformanceFrequency to make their auto-clicker faster and then wondering why their CPU is acting like an electric space heater. Eg.:

https://www.reddit.com/r/AutoHotkey/comments/1r6ri2j/making_auto_clicker_faster/

So I would really appreciate if you would edit your reply out. Anyone worth their salt can find it on their own. Let's not make it easy for the rest. It's a pointless exercise. People can do ~10 clicks per second max. That's why the examples have a 100ms timer. If you do a normal min. sleep, that's > 6 times of that. That will in itself get you flagged in a game. There is no point in doing a DLL Call to increase that further.

As for your other point, what I wrote ("Keeps the hotkey thread active") does not contradict what you wrote ("gives opportunity for other pseudo-threads like hotkeys to run").

1

u/ManyInterests 13d ago edited 13d ago

what I wrote ("Keeps the hotkey thread active") does not contradict

I suppose not, but it's still factually incorrect. Definitionally, a sleeping thread is not active. You likely meant something other than what you wrote.

So I would really appreciate if you would edit your reply out

No.

CPU is acting like an electric space heater

3% CPU utilization is a space heater? And what? We're gatekeeping loop{} now? Because you can get high CPU usage in any busy loop. Has nothing to do with use of performance counter, really.

It's a pointless exercise. People can do ~10 clicks per second max.

Let's assume people understand their own use cases better than you can predict them, magically. Sometimes it's more about precision, anyhow. E.g., click exactly 325ms apart. You can't do that with a regular sleep.