Skip to main content

Macros & Keybinds

LiquidBounce features a sophisticated keybind system that allows you to bind keyboard keys and mouse buttons to modules and commands. The system supports multiple bind actions, modifiers, and complex key combinations.

Overview

The keybind system provides:

Flexible Bindings

Bind keyboard keys, mouse buttons, and scancodes

Bind Actions

Toggle, Hold, or Smart activation modes

Modifiers

Support for Shift, Ctrl, Alt, and Super modifiers

Module Integration

Every module has a configurable keybind

InputBind Structure

The core of the keybind system is the InputBind class:
data class InputBind(
    val boundKey: InputConstants.Key,
    val action: BindAction,
    val modifiers: Set<Modifier>,
)

Components

  • boundKey: The key or button to bind (keyboard key, mouse button, or scancode)
  • action: How the bind behaves (Toggle, Hold, or Smart)
  • modifiers: Required modifier keys (Shift, Ctrl, Alt, Super)

Bind Actions

Three bind action modes are available:
Flips the state when pressed:
BindAction.TOGGLE
  • Press once: Enable
  • Press again: Disable
  • Most common for modules

Implementation

From InputBind.kt:181:
fun getNewState(event: KeyboardKeyEvent, currentState: Boolean): Boolean {
    if (!matchesKey(event.keyCode, event.scanCode)) {
        return currentState
    }
    
    val eventAction = event.action
    return when (eventAction) {
        GLFW.GLFW_PRESS if mc.screen == null -> when (action) {
            BindAction.TOGGLE -> !currentState
            BindAction.HOLD, BindAction.SMART -> true
        }
        GLFW.GLFW_RELEASE -> when (action) {
            BindAction.HOLD -> false
            BindAction.TOGGLE, BindAction.SMART -> currentState
        }
        else -> currentState
    }
}
Binds only activate when no screen is open (prevents conflict with menus).

Modifiers

Supported modifier keys:
enum class Modifier(
    override val tag: String,
    val bitMask: Int,
    vararg val keyCodes: Int
): Tagged {
    SHIFT("Shift", GLFW.GLFW_MOD_SHIFT, KEY_LSHIFT, KEY_RSHIFT),
    CONTROL("Control", GLFW.GLFW_MOD_CONTROL, KEY_LCONTROL, KEY_RCONTROL),
    ALT("Alt", GLFW.GLFW_MOD_ALT, KEY_LALT, KEY_RALT),
    SUPER("Super", GLFW.GLFW_MOD_SUPER, KEY_LSUPER, KEY_RSUPER)
}

Platform-Specific Names

Modifiers show platform-appropriate symbols:
val platformRenderName: String get() = when (Util.getPlatform()) {
    Util.OS.WINDOWS -> when (this) {
        CONTROL -> "Ctrl"
        SUPER -> "⊞"  // Windows key
        else -> tag
    }
    Util.OS.OSX -> when (this) {
        SHIFT -> "⇧"
        CONTROL -> "^"
        ALT -> "⌥"
        SUPER -> "⌘"  // Command key
    }
    else -> tag
}

Checking Modifiers

Check if required modifiers are active:
fun matchesModifiers(mods: Int): Boolean {
    return this.modifiers.all { it.isActive(mods) }
}

fun Modifier.isActive(modifiers: Int) = 
    modifiers and this.bitMask != 0

Creating Bindings

Basic Bind

// Bind to a specific key
val bind = InputBind(
    InputConstants.Type.KEYSYM,
    GLFW.GLFW_KEY_R,
    BindAction.TOGGLE
)

// Bind by name
val bind = InputBind("key.keyboard.r")

Bind with Modifiers

// Ctrl+R
val bind = InputBind(
    boundKey = InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_R),
    action = BindAction.TOGGLE,
    modifiers = setOf(InputBind.Modifier.CONTROL)
)

// Shift+Alt+F
val bind = InputBind(
    boundKey = InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_F),
    action = BindAction.HOLD,
    modifiers = setOf(
        InputBind.Modifier.SHIFT,
        InputBind.Modifier.ALT
    )
)

Mouse Bindings

// Right mouse button
val bind = InputBind(
    InputConstants.Type.MOUSE,
    GLFW.GLFW_MOUSE_BUTTON_RIGHT,
    BindAction.TOGGLE
)

// Middle mouse + Ctrl
val bind = InputBind(
    boundKey = InputConstants.Type.MOUSE.getOrCreate(GLFW.GLFW_MOUSE_BUTTON_MIDDLE),
    action = BindAction.HOLD,
    modifiers = setOf(InputBind.Modifier.CONTROL)
)

Module Integration

Every ClientModule has a keybind:
open class ClientModule(
    name: String,
    category: ModuleCategory,
    bind: Int = InputConstants.UNKNOWN.value,
    bindAction: BindAction = BindAction.TOGGLE,
    state: Boolean = false,
    // ...
) {
    internal val bindValue = bind("Bind", InputBind(
        InputConstants.Type.KEYSYM,
        bind,
        bindAction
    ))
    val bind get() = bindValue.get()
}

Module Keybind Usage

// Access module's bind
val flyBind = ModuleFly.bind

// Modify module's bind
ModuleFly.bindValue.bind("key.keyboard.f")

// Set bind with modifiers
ModuleFly.bindValue.bind(
    key = InputConstants.Type.KEYSYM.getOrCreate(GLFW.GLFW_KEY_V),
    action = BindAction.HOLD,
    modifiers = setOf(InputBind.Modifier.CONTROL)
)

// Unbind
ModuleFly.bindValue.unbind()

Event Matching

The system matches events to bindings:

Keyboard Events

// Check if key press matches
fun matchesKeyPress(event: KeyboardKeyEvent): Boolean {
    return event.action == GLFW.GLFW_PRESS
        && matchesKey(event.keyCode, event.scanCode)
        && matchesModifiers(event.mods)
}

// Check if release affects this bind
fun matchesKeyRelease(event: KeyboardKeyEvent): Boolean {
    if (event.action != GLFW.GLFW_RELEASE) return false
    
    val keyReleased = matchesKey(event.keyCode, event.scanCode)
    val modifierReleased = event.key.toModifierOrNull()
        .let { it in modifiers && !it!!.isAnyPressed }
    
    return keyReleased || modifierReleased
}

Mouse Events

// Check if mouse press matches
fun matchesMousePress(event: MouseButtonEvent): Boolean {
    return event.action == GLFW.GLFW_PRESS
        && matchesMouse(event.button)
        && matchesModifiers(event.mods)
}

// Check if release affects this bind
fun matchesMouseRelease(event: MouseButtonEvent): Boolean {
    if (event.action != GLFW.GLFW_RELEASE) return false
    
    val buttonReleased = matchesMouse(event.button)
    val modifierReleased = event.key.toModifierOrNull()
        .let { it in modifiers && !it!!.isAnyPressed }
    
    return buttonReleased || modifierReleased
}
Releasing a required modifier also deactivates the bind in Hold mode.

Helper Functions

Bind Management

// Bind to a named key
fun Value<InputBind>.bind(name: String) = 
    set(get().copy(boundKey = inputByName(name)))

// Bind with full configuration
fun Value<InputBind>.bind(
    key: InputConstants.Key,
    action: BindAction,
    modifiers: Set<Modifier>
) = set(get().copy(
    boundKey = key,
    action = action,
    modifiers = modifiers
))

// Unbind
fun Value<InputBind>.unbind() = set(InputBind.UNBOUND)

Rendering

Generate display text for a bind:
fun InputBind.renderText(): Component = buildList {
    // Main key
    add(variable(boundKey.displayName).bold())
    
    // Modifiers
    if (modifiers.isNotEmpty()) {
        modifiers.forEach {
            add(regular(" + "))
            add(variable(it.platformRenderName))
        }
    }
    
    // Action type
    add(regular(" ("))
    add(variable(action.tag))
    add(regular(")"))
}.asText()
Output examples:
  • R (Toggle)
  • Ctrl + V (Hold)
  • Shift + Alt + F (Smart)

Special Keys

Unbound Key

companion object {
    @JvmField
    val UNBOUND = InputBind(
        InputConstants.UNKNOWN,
        BindAction.TOGGLE,
        emptySet()
    )
}

val isUnbound: Boolean
    get() = this.boundKey == InputConstants.UNKNOWN

Key Names

val keyName: String
    get() = when {
        isUnbound -> "None"
        else -> this.boundKey.name
            .split('.')
            .drop(2)  // Drop "key.keyboard" or "key.mouse"
            .joinToString(separator = "_")
            .uppercase()
    }
Examples:
  • key.keyboard.rR
  • key.mouse.leftLEFT
  • key.keyboard.left.shiftLEFT_SHIFT

Configuration

Binds are stored in module configurations:
{
  "Fly": {
    "Bind": {
      "key": "key.keyboard.f",
      "action": "Toggle",
      "modifiers": []
    }
  },
  "Sprint": {
    "Bind": {
      "key": "key.keyboard.v",
      "action": "Hold",
      "modifiers": ["Control"]
    }
  }
}

Code References

InputBind.kt

Complete keybind system - line 51
utils/input/InputBind.kt (339 lines)

ClientModule.kt

Module bind integration - line 77
features/module/ClientModule.kt

Best Practices

1

Choose Appropriate Actions

Use Toggle for permanent states, Hold for temporary actions, Smart for flexibility
2

Avoid Conflicts

Check for existing binds before assigning to prevent conflicts
3

Use Modifiers Wisely

Modifiers allow more binds without conflicts, but too many make them hard to use
4

Consider Vanilla Binds

Avoid overriding important vanilla Minecraft keybinds
5

Platform Compatibility

Test binds on different platforms as some keys may not be available
6

Document Custom Binds

If creating macros, document the key combinations for users

Build docs developers (and LLMs) love