Skip to content

Keyboard and mouse

Key codes are SDL scancodes (keyboard) or aqua.keys.MOUSE1..MOUSE5 (same encoding as C++ KeyState).

Common entries: A–Z, N0–N9, F1–F12, TAB, ESC, SPACE, ENTER, INSERT, arrows, modifiers, MOUSE1..MOUSE5, MOUSE_BASE, UNBOUND.

API Description
aqua.input.is_down(code) Held, raw - true even while the key is being typed into a chat/console box (false for UNBOUND)
aqua.input.is_bind_down(code) Held and binds are live right now - is_down minus the typing case (false for UNBOUND)
aqua.input.is_key_down(scancode) Keyboard held
aqua.input.is_mouse_down(button) 1 left, 2 middle, 3 right
aqua.input.key_pressed(scancode) Rising edge this frame (sticky until end of stage 12)
aqua.input.mouse_clicked(button) Rising edge this frame
aqua.input.mouse() x, y, buttons - SDL coords + button bitmask
aqua.input.wheel() Vertical wheel notches this frame (positive = up, 0 idle)
aqua.input.key_name(code) Display label ("E", "MOUSE1", …)
aqua.input.scoreboard_open() TAB held (same check as the built-in scoreboard)
aqua.input.menu_open() aqua menu visible
aqua.is_menu_open() alias of menu visible
aqua.input.console_open() CS2’s own console is up. Read from a value sampled once per frame on the game thread, so it is safe from on_draw too
aqua.input.binds_suppressed() True while a keypress is being typed rather than meant as a bind (console open, menu open, or any script lock)
aqua.input.lock() Free OS cursor, disable relative mouse, block game mouse/keyboard (same capture path as the menu). Per-script; refcounted across scripts.
aqua.input.unlock() Release this script’s lock. Menu open / other scripts may still capture.
aqua.input.set_locked(bool) lock() if true, unlock() if false
aqua.input.is_locked() Whether this script currently holds lock()
aqua.input.is_capture_active() Game input is blocked right now (menu open or any script lock)
local K = aqua.keys
function on_draw()
if aqua.input.menu_open() then return end
if aqua.input.key_pressed(K.F6) then
print("F6 pressed")
end
if aqua.input.is_down(K.MOUSE1) then
-- held
end
if aqua.input.mouse_clicked(1) then
local mx, my, buttons = aqua.input.mouse()
print("click", mx, my)
end
if aqua.input.scoreboard_open() and aqua.rate_limit("sb", 0.2) then
-- inject panorama payload here
end
end

is_down / is_key_down / key_pressed are raw: they report the physical key, and they cannot tell a bind from a key being typed into CS2’s console. That is deliberate - a script asking for raw key state gets raw key state - but it means a script bind on T will also fire while you type T into the console.

If your key is acting as a bind, use is_bind_down (or gate on binds_suppressed()), which is dead while the console or the aqua menu owns the keyboard:

-- bind: must not fire while typing into the console
if aqua.input.is_bind_down(cfg.key) then
toggle_thing()
end
-- equivalent, when you need the raw edge helpers
if aqua.input.key_pressed(K.T) and not aqua.input.binds_suppressed() then
toggle_thing()
end

The console half comes from the engine itself (GameUIService_001::IsConsoleVisible), not from a guess about focus, so it is exact rather than heuristic. Both are read from a value sampled once per frame on the game thread, so calling them from on_draw is safe.

Wheel: latched for the whole FSN burst like the key/mouse edges, so every script sees the same value from either callback and one script reading it does not starve another. It is fed from every wheel event, blocked or not - a script cycling something with the wheel needs it while the game owns input. The event still reaches the game, so a bare-wheel binding shares the input with weapon switch; gate on a modifier (or aqua.input.lock()) if that matters.

local steps = aqua.input.wheel()
if steps ~= 0 and aqua.input.is_key_down(aqua.keys.LALT) then
station = station + (steps > 0 and 1 or -1)
end

Same mechanism the aqua menu uses when open:

  • Show the free OS cursor and turn off window relative-mouse mode
  • Drop mouse motion/buttons/wheel and keyboard/text events so the game does not see them
  • Script-held locks are refcounted; STOP / unload auto-unlocks that script
  • Opening the menu while locked keeps capture; closing the menu while still locked keeps capture
  • INSERT still toggles the menu; it does not force-unlock scripts
  • Does not open the aqua UI or claim menu click regions - only free cursor + block game input
  • Scripts can still read aqua.input.* / draw overlays while locked
local locked = false
function on_frame_stage_notify(stage)
if stage ~= 12 then return end
if aqua.input.key_pressed(aqua.keys.F7) then
locked = not locked
aqua.input.set_locked(locked)
print(locked and "input locked" or "input unlocked")
end
end
-- Always safe: unlock on stop is automatic, but you can unlock early:
-- aqua.input.unlock()