Win API

This API exposes a small subset of the Win32 window, clipboard, keyboard, mouse and messaging functions to Lua:

  • Work with windows via handles (hwnd as integer).

  • Inspect window rectangles and sizes.

  • Check if a window is foreground or “active”.

  • Read / write text to the clipboard (UTF-8).

  • Look up the thread & process IDs for a window.

  • Send messages, keys, and characters to a window.

  • Send global keyboard and mouse input via SendInput.


Window Lookup & Info

find_window(title [, class]) -> hwnd | nil

Search for a top-level window.

  • title — window title to match (UTF-8 string).

  • class — optional window class name (UTF-8 string).

If both are provided, both must match. Returns hwnd (integer) or nil if not found.


get_window_size(hwnd) -> width, height | nil, nil

Get the window’s outer size in pixels.

  • hwnd — window handle (integer).

Returns:

  • width, height (integers), or

  • nil, nil if the handle is invalid or the call fails.


get_window_rect(hwnd) -> x, y, width, height | nil, nil, nil, nil

Get the window’s full rectangle in screen coordinates.

  • x, y — top-left screen coordinates.

  • width, height — size in pixels.

Returns all nil on failure.


Window Focus & Activity

is_foreground_window(hwnd) -> bool

Checks if hwnd is the current foreground window.


is_window_active(hwnd) -> bool

Checks if a window is “active” in a broad sense:

  • is a valid window,

  • is visible,

  • is not minimized.

Returns true or false.


Window Text & Class

get_window_title(hwnd) -> string | nil

Get the window’s title as a UTF-8 string.

  • Returns "" for empty titles.

  • Returns nil if the handle is invalid.


get_window_class(hwnd) -> string | nil

Get the window’s class name.

  • Returns "" if class name can’t be read.

  • Returns nil if the handle is invalid.


set_foreground_window(hwnd) -> bool

Attempts to bring the given window to the foreground.

Returns true on success, false otherwise.


Clipboard

All clipboard operations use UTF-8 strings.

copy_to_clipboard(text) -> bool

Copies a Lua string into the system clipboard.

  • text — UTF-8 string.

  • Returns true on success, false on failure.


copy_from_clipboard() -> string

Reads UTF-8 text from the system clipboard.

  • Returns "" (empty string) if:

    • clipboard cannot be opened,

    • the clipboard doesn’t contain text, or

    • any error occurs.


Thread / Process Info

get_window_thread_process_id(hwnd) -> thread_id, process_id | nil, nil

Returns IDs associated with a given window.

  • thread_id — thread that owns the window.

  • process_id — process that owns the thread.

If the handle is invalid or the IDs can’t be obtained, returns nil, nil.


Messages & Keys

post_message(hwnd, msg, wparam, lparam) -> bool

Low-level API to post a message to a window.

  • hwnd — window handle.

  • msg — message ID (e.g., 0x0010 for WM_CLOSE).

  • wparam, lparam — integer parameters.

Returns true on success.


Global key input (SendInput)

These functions send global keyboard input (not scoped to a specific window). Use when you simply want to simulate key presses.

Virtual keys are integers (e.g. 0x41 for A, 0x11 for Ctrl, 0x0D for Enter).

win_key_down(vk)

Simulate key down for virtual key vk.

win_key_up(vk)

Simulate key up for virtual key vk.

win_key_press(vk [, delay_ms])

Press and release a key, with an optional delay.

  • vk — virtual key code.

  • delay_ms — optional delay in milliseconds between down and up (default ~30, clamped to 0..1000).


Message-based character & key sending

These functions target a specific window using messages (WM_CHAR, WM_KEYDOWN, WM_KEYUP).

send_char(hwnd, text) -> bool

Sends a single character via WM_CHAR.

  • text — Lua string; only the first UTF-16 code unit is used, which is fine for most BMP characters.

Returns true on success.

send_key(hwnd, vk) -> bool

Sends a key to a specific window using WM_KEYDOWN and WM_KEYUP.

  • vk — virtual key code.

Returns true if both messages were posted successfully.


Mouse Input

All mouse functions send global input via SendInput.

Coordinates are in screen pixels (primary monitor), unless noted otherwise.

mouse_move(x, y)

Moves the cursor to absolute screen coordinates.

  • (0,0) is top-left of the primary display.

  • Coordinates are converted to the normalized range expected by SendInput.


mouse_move_relative(dx, dy)

Moves the cursor relative to its current position.

  • dx — horizontal delta (pixels).

  • dy — vertical delta (pixels).


mouse_left_click()

Performs a left button click at the current cursor position.

mouse_right_click()

Performs a right button click.

mouse_middle_click()

Performs a middle button click.

Each click sends a down event, waits ~10 ms, then sends an up event.


mouse_scroll(amount)

Scrolls the mouse wheel vertically.

  • amount is in “notches”:

    • positive → scroll up

    • negative → scroll down

Example: mouse_scroll(3) scrolls up 3 notches.


Util

get_tickcount64()

  • GetTickCount64()


Usage Examples

Find a window and print info

local title   = "Untitled - Notepad"
local hwnd    = find_window(title)

if not hwnd then
  print("Window not found:", title)
  return
end

print("HWND:", string.format("0x%X", hwnd))

local x, y, w, h = get_window_rect(hwnd)
print("Rect:", x, y, w, h)

local winTitle = get_window_title(hwnd) or "<nil>"
local winClass = get_window_class(hwnd) or "<nil>"
print("Title:", winTitle)
print("Class:", winClass)

local tid, pid = get_window_thread_process_id(hwnd)
print("Thread ID:", tid, "Process ID:", pid)

Clipboard

copy_to_clipboard("Hello from Lua! こんにちは 🌸")

local txt = copy_from_clipboard()
print("Clipboard:", txt)

Global keystrokes

-- Type 'HELLO'
local letters = { 0x48, 0x45, 0x4C, 0x4C, 0x4F } -- H E L L O
for _, vk in ipairs(letters) do
  win_key_press(vk, 40)
end

-- Ctrl+V
local VK_CONTROL = 0x11
local VK_V       = 0x56

win_key_down(VK_CONTROL)
win_key_press(VK_V, 30)
win_key_up(VK_CONTROL)

Message-based typing into a window

local hwnd = find_window("Untitled - Notepad")
if not hwnd then return end

-- make sure Notepad has focus (if you expose set_foreground_window)
set_foreground_window(hwnd)

send_char(hwnd, "H")
send_char(hwnd, "i")
send_char(hwnd, " ")
send_char(hwnd, "🌸")

local VK_RETURN = 0x0D
send_key(hwnd, VK_RETURN)

Mouse movement & clicks

-- Move to near top-left, left-click
mouse_move(100, 100)
mouse_left_click()

-- Move a bit to the right and right-click
mouse_move_relative(80, 0)
mouse_right_click()

-- Move down and middle-click
mouse_move_relative(0, 60)
mouse_middle_click()

-- Scroll up then down
mouse_scroll(3)
mouse_scroll(-3)

Full API Test

local TARGET_TITLE = "Untitled - Notepad"  -- change to your target window

local function has_global(name)
    return _G[name] ~= nil
end

local function get_target_hwnd()
    if not has_global("find_window") then
        log("ERROR: find_window is not available.")
        return nil
    end

    local hwnd = find_window(TARGET_TITLE)
    if not hwnd then
        log("Window not found:", TARGET_TITLE)
        return nil
    end

    log("Found hwnd:", string.format("0x%X", hwnd))

    if has_global("set_foreground_window") then
        local ok = set_foreground_window(hwnd)
        log("set_foreground_window:", ok)
    else
        log("set_foreground_window not available, skipping.")
    end

    return hwnd
end

local function test_mouse_basic()
    log("=== TEST: mouse movement & clicks ===")
    log("Move your eyes to where the cursor goes. :)")

    -- Move to approximate top-left of the primary screen
    mouse_move(100, 100)
    mouse_left_click()
    log("  mouse_move(100,100) + left click")

    -- Move a bit to the right and right-click
    mouse_move_relative(80, 0)
    mouse_right_click()
    log("  mouse_move_relative(80,0) + right click")

    -- Move a bit down and middle-click
    mouse_move_relative(0, 60)
    mouse_middle_click()
    log("  mouse_move_relative(0,60) + middle click")

    -- Scroll up then down a bit
    mouse_scroll(3)   -- up 3 notches
    mouse_scroll(-3)  -- down 3 notches
    log("  mouse_scroll(3) then mouse_scroll(-3)")
end

local function test_send_char_key(hwnd)
    if not hwnd then
        log("No hwnd, skipping send_char/send_key test.")
        return
    end

    if not has_global("send_char") or not has_global("send_key") then
        log("send_char / send_key not available, skipping.")
        return
    end

    log("=== TEST: send_char / send_key ===")
    log("Make sure the target window has a focused text field.")

    -- Send "Hi " + emoji via WM_CHAR
    send_char(hwnd, "H")
    send_char(hwnd, "i")
    send_char(hwnd, " ")
    send_char(hwnd, "🌸") -- only 

    -- Press Enter via WM_KEYDOWN/UP
    local VK_RETURN = 0x0D
    local ok = send_key(hwnd, VK_RETURN)
    log("send_key(VK_RETURN) ->", ok)
end

function main()
    log("Target window title:", TARGET_TITLE)

    local hwnd = get_target_hwnd()

    test_mouse_basic()
    test_send_char_key(hwnd)

    log("=== winapi_input_test.lua done ===")
    return 1;
end

Last updated