Extend Your Scripts/Programs with Lua!

Post your working scripts, libraries and tools.
User avatar
Delta Pythagorean
Posts: 628
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Extend Your Scripts/Programs with Lua!

27 Oct 2023, 16:04

Lua.ahk
You heard that right. This took way too freaking long for me and gave me several headaches, but it's finally here.

Please do note, I do not have all functions added. Just a lot of the basics and some advanced ones.

Code: Select all

/**
 * # Lua.ahk
 * Extend *your* AHK program with Lua!
 *
 * ## Requirements
 * | Library | Tested Version | External Link |
 * | --- | --- | --- |
 * | AutoHotkey | 2.0.6 | https://www.autohotkey.com/download |
 * | Lua | 5.4.2 (Release 1) | https://luabinaries.sourceforge.net |
 *
 * # How To Install
 * 1. Download this script into your `/lib/` folder for your current project or into your AutoHotkey library directory.
 * Read more here: https://www.autohotkey.com/docs/v2/Scripts.htm#lib
 * 2. Use `#include` to include the library into your AHK program.
 * 3. Validate you have `lua54.dll` with your project. Make sure to use `#DllLoad` and set it to the path of the Lua DLL, for example: `#DllLoad %A_ScriptDir%\lua54.dll`
 * 4. Look up a tutorial for using Lua in C (with some minor adjustments to the syntax) and you're on your way.
 *
 * # Notes
 * - Lua does not have *all* of the functions defined publically for the Lua DLL.
 *   Therefore, translating the functions directly to a DllCall won't be easy.
 *   There are some functions that are just macros (read the Macros section below for more information),
 *   However some functions downright don't exist externally, such as `lua_call`, `lua_pcall`, and many others.
 *   So directly translating C code into AHK won't be a perfect solution.
 * - I have provided a list of functions that the lua54 DLL has and removed the functions that are currently present in this file.
 *   You may add them yourself if you so desire.
 * - I have not tested all functions (within this file) to see if they work.
 *   I only have used various examples strewn about within Lua's documentation to see if they work.
 *   A lot of the functions do in fact work :)
*/

#Requires AutoHotkey v2.0

; Attempt to load the lua DLL if present.
#DllLoad *i lua54.dll

; =============================================================================
; # Global Variables

; This is not defined in Lua's source, this is a simple NULL variable added to AHK.
; In C, NULL is a null pointer with the position of 0.
; In AHK, I recommend to use a blank string.
global NULL                     := ""

global LUA_MULTRET              := -1

global LUA_OK                   := 0
global LUA_YIELD                := 1
global LUA_ERRRUN               := 2
global LUA_ERRSYNTAX            := 3
global LUA_ERRMEM               := 4
global LUA_ERRERR               := 5

/*
 * LUAI_MAXSTACK limits the size of the Lua stack.
 * Its only purpose is to stop Lua from consuming unlimited stack
 * space (and to reserve some numbers for pseudo-indices).
 * (It must fit into max(size_t)/32 and max(int)/2.)
*/
; SO. The provided lua DLL is compiled in 32 bit.
; Regardless of AHK's bit size (32 or 64) Lua's stack size will always be 32 bit.
global LUAI_MAXSTACK            := 1000000 ; A_PtrSize == 4 ? 1000000 : 15000

; minimum Lua stack available to a C function
global LUA_MINSTACK             := 20

global LUA_REGISTRYINDEX        := (-LUAI_MAXSTACK - 1000)

global LUA_TNONE                := -1

global LUA_TNIL                 := 0
global LUA_TBOOLEAN             := 1
global LUA_TLIGHTUSERDATA       := 2
global LUA_TNUMBER              := 3
global LUA_TSTRING              := 4
global LUA_TTABLE               := 5
global LUA_TFUNCTION            := 6
global LUA_TUSERDATA            := 7
global LUA_TTHREAD              := 8

global LUA_NUMTYPES             := 9

global LUA_RIDX_MAINTHREAD      := 1
global LUA_RIDX_GLOBALS         := 2
global LUA_RIDX_LAST            := LUA_RIDX_GLOBALS

global LUA_NOREF                := -2
global LUA_REFNIL               := -1

; =============================================================================
; # To-Be-Created Functions

/*
lua_arith
lua_atpanic
lua_checkstack
lua_compare
lua_concat
lua_dump
lua_gc
lua_getallocf
lua_getfield
lua_gethook
lua_gethookcount
lua_gethookmask
lua_geti
lua_getinfo
lua_getiuservalue
lua_getlocal
lua_getmetatable
lua_getstack
lua_isyieldable
lua_len
lua_load
lua_newstate
lua_newthread
lua_newuserdatauv
lua_next
lua_pushboolean
lua_pushfstring
lua_pushlightuserdata
lua_pushlstring
lua_pushthread
lua_pushvalue
lua_pushvfstring
lua_rawequal
lua_rawgetp
lua_rawsetp
lua_resetthread
lua_resume
lua_setallocf
lua_setcstacklimit
lua_sethook
lua_seti
lua_setiuservalue
lua_setlocal
lua_setmetatable
lua_setwarnf
lua_status
lua_stringtonumber
lua_tocfunction
lua_toclose
lua_tointegerx
lua_tothread
lua_touserdata
lua_upvalueid
lua_upvaluejoin
lua_version
lua_warning
lua_xmove
lua_yieldk
lual_addgsub
lual_addlstring
lual_addstring
lual_addvalue
lual_argerror
lual_buffinit
lual_buffinitsize
lual_callmeta
lual_checkany
lual_checkinteger
lual_checklstring
lual_checknumber
lual_checkoption
lual_checkstack
lual_checkudata
lual_checkversion_
lual_error
lual_execresult
lual_fileresult
lual_getmetafield
lual_getsubtable
lual_gsub
lual_len
lual_loadbufferx
lual_loadstring
lual_newmetatable
lual_optinteger
lual_optlstring
lual_optnumber
lual_prepbuffsize
lual_pushresult
lual_pushresultsize
lual_requiref
lual_setfuncs
lual_setmetatable
lual_testudata
lual_tolstring
lual_traceback
lual_typeerror
lual_where
*/

; =============================================================================
; # Functions

luaL_loadfilex(L, filename, mode) => DllCall("lua54.dll\luaL_loadfilex", "ptr", L, "astr", String(filename), mode == null ? "int" : "str", mode == null ? 0 : String(mode))
luaL_newstate() => DllCall("lua54.dll\luaL_newstate", "ptr")
luaL_openlibs(L) => DllCall("lua54.dll\luaL_openlibs", "ptr", L)
luaL_checktype(L, arg, t) => DllCall("lua54.dll\luaL_checktype", "ptr", L, "int", Integer(arg), "int", Integer(t))

; As of currently, this doesn't seem to do what I want it to do. It throws an access violation.
; Perhaps I'm using these functions wrong/in the wrong place. I don't know.
; For now, it's up for usage and you're free to run with it.
; Just be aware.
luaL_ref(L, t) => DllCall("lua54.dll\luaL_ref", "ptr", L, "int", t)
luaL_unref(L, t) => DllCall("lua54.dll\luaL_unref", "ptr", L, "int", t)

lua_absindex(L, idx) => DllCall("lua54.dll\lua_absindex", "ptr", L, "int", idx)
lua_close(L) => DllCall("lua54.dll\lua_close", "ptr", L)
lua_copy(L, fromidx, toidx) => DllCall("lua54.dll\lua_copy", "ptr", L, "int", Integer(fromidx), , "int", Integer(toidx))
lua_createtable(L, narr, nrec) => DllCall("lua54.dll\lua_createtable", "ptr", L, "int", Integer(narr), "int", Integer(nrec))
lua_error(L) => DllCall("lua54.dll\lua_error", "ptr", L)
lua_getglobal(L, name) => DllCall("lua54.dll\lua_getglobal", "ptr", L, "astr", name)
lua_getupvalue(L, funcindex, n) => DllCall("lua54.dll\lua_getupvalue", "ptr", L, "int", funcindex, "int", n)
lua_gettable(L, index) => DllCall("lua54.dll\lua_gettable", "ptr", L, "int", Integer(index))
lua_gettop(L) => DllCall("lua54.dll\lua_gettop", "ptr", L)
lua_iscfunction(L, index) => DllCall("lua54.dll\lua_iscfunction", "ptr", L, "int", Integer(index))
lua_pcallk(L, nargs, nresults, msgh, ctx, k) => DllCall("lua54.dll\lua_pcallk", "ptr", L, "int", Integer(nargs), "int", Integer(nresults), "int", Integer(msgh), "int", ctx, k == null ? "int" : "ptr", k || 0)
lua_pushcclosure(L, f, n) => DllCall("lua54.dll\lua_pushcclosure", "ptr", L, "ptr", f, "int", Integer(n))
lua_pushnumber(L, n) => DllCall("lua54.dll\lua_pushnumber", "ptr", L, "double", Float(n))
lua_pushinteger(L, n) => DllCall("lua54.dll\lua_pushinteger", "ptr", L, "int", Integer(n))
lua_pushliteral(L, s) => DllCall("lua54.dll\lua_pushliteral", "ptr", L, "astr", String(s))
lua_pushnil(L) => DllCall("lua54.dll\lua_pushnil", "ptr", L)
lua_pushstring(L, s) => DllCall("lua54.dll\lua_pushstring", "ptr", L, "astr", String(s))
lua_rawget(L, index) => DllCall("lua54.dll\lua_rawget", "ptr", L, "int", Integer(index))
lua_rawgeti(L, index, n) => DllCall("lua54.dll\lua_rawgeti", "ptr", L, "int", Integer(index), "int", Integer(n))
lua_rawlen(L, index) => DllCall("lua54.dll\lua_rawlen", "ptr", L, "int", index)
lua_rawset(L, index) => DllCall("lua54.dll\lua_rawset", "ptr", L, "int", Integer(index))
lua_rawseti(L, index, i) => DllCall("lua54.dll\lua_rawseti", "ptr", L, "int", Integer(index), "int", Integer(i))
lua_requiref(L, modname, openf, glb) => DllCall("lua54.dll\lua_rawseti", "ptr", L, "str", String(modname), "ptr", openf, "int", Integer(glb))
lua_rotate(L, idx, n) => DllCall("lua54.dll\lua_rotate", "ptr", L, "int", Integer(idx), "int", Integer(n))
lua_setfield(L, index, k) => DllCall("lua54.dll\lua_setfield", "ptr", L, "int", Integer(index), "astr", String(k))
lua_setglobal(L, name) => DllCall("lua54.dll\lua_setglobal", "ptr", L, "astr", name)
lua_setupvalue(L, funcindex, n) => DllCall("lua54.dll\lua_setupvalue", "ptr", L, "int", funcindex, "int", n)
lua_settable(L, index) => DllCall("lua54.dll\lua_settable", "ptr", L, "int", Integer(index))
lua_settop(L, index) => DllCall("lua54.dll\lua_settop", "ptr", L, "int", Integer(index))
lua_toboolean(L, index) => DllCall("lua54.dll\lua_toboolean", "ptr", L, "int", Integer(index))
lua_tointeger(L, index) => DllCall("lua54.dll\lua_tointegerx", "ptr", L, "int", Integer(index), "int", 0)

; NOTE:
; lua_tolstring returns 0 (or NULL) if it couldn't cast it to being a string.
; To make it easier for AHK to handle (and to not get an error thrown in StrGet())
; I've forced it to return a blank string if it could not return a string.
lua_tolstring(L, index, len)
{
	result := DllCall("lua54.dll\lua_tolstring", "ptr", L, "int", Integer(index), len == null ? "int" : "int*", len == null ? 0 : Integer(len))
	; We must check if the value returned was NULL, otherwise StrGet will silent crash AHK.
	return result == 0 ? "" : StrGet(result, "UTF-8")
}

lua_tonumberx(L, index, &isnum?) => DllCall("lua54.dll\lua_tonumberx", "ptr", L, "int", Integer(index), !IsSet(isnum) ? "int" : "int*", !IsSet(isnum) ? 0 : &isnum, "double")
lua_topointer(L, index) => Format("<pointer: {:#x}>", DllCall("lua54.dll\lua_topointer", "ptr", L, "int", Integer(index)))
lua_type(L, index) => DllCall("lua54.dll\lua_type", "ptr", L, "int", Integer(index))
lua_typename(L, tp) => StrGet(DllCall("lua54.dll\lua_typename", "ptr", L, "int", Integer(tp)), "UTF-8")
lua_callk(L, nargs, nresults, ctx, k) => DllCall("lua54.dll\lua_typename", "ptr", L, "int", Integer(nargs), "int", Integer(nresults), "int", ctx, k == null ? "int" : "ptr", k || 0)
luaopen_base(L) => DllCall("lua54.dll\luaopen_base", "ptr", L)
luaopen_package(L) => DllCall("lua54.dll\luaopen_package", "ptr", L)
luaopen_coroutine(L) => DllCall("lua54.dll\luaopen_coroutine", "ptr", L)
luaopen_table(L) => DllCall("lua54.dll\luaopen_table", "ptr", L)
luaopen_io(L) => DllCall("lua54.dll\luaopen_io", "ptr", L)
luaopen_os(L) => DllCall("lua54.dll\luaopen_os", "ptr", L)
luaopen_string(L) => DllCall("lua54.dll\luaopen_string", "ptr", L)
luaopen_math(L) => DllCall("lua54.dll\luaopen_math", "ptr", L)
luaopen_utf8(L) => DllCall("lua54.dll\luaopen_utf8", "ptr", L)
luaopen_debug(L) => DllCall("lua54.dll\luaopen_debug", "ptr", L)

; =============================================================================
; # Macro Functions
; These functions are not present (or not defined publicly for use) in the Lua DLL.
; However, they are macros in Lua's source code, available to read/use for yourself.
; Seeing as Lua has provided these for us, we can create them here.
; I don't know of any others, but it's pretty easy to translate them from C into AHK.

luaL_loadfile(L, filename) => luaL_loadfilex(L, filename, null)
luaL_dofile(L, filename) => (luaL_loadfile(L, filename) || lua_pcallk(L, 0, LUA_MULTRET, 0, 0, null))
lua_call(L, n, r) => lua_callk(L, n, r, 0, null)
lua_pcall(L, n, r, f) => lua_pcallk(L, n, r, f, 0, null)
lua_tostring(L, index) => lua_tolstring(L, index, null)
lua_tonumber(L, index) => lua_tonumberx(L, index)
lua_newtable(L) => lua_createtable(L, 0, 0)
lua_pop(L, n) => lua_settop(L, 0 - n - 1)
lua_register(L, n, f) => (lua_pushcfunction(L, f), lua_setglobal(L, n))
lua_pushcfunction(L, f) => lua_pushcclosure(L, f, 0)
lua_isnil(L, n) => (lua_type(L, n) == LUA_TNIL)
lua_isboolean(L, n) => (lua_type(L, n) == LUA_TBOOLEAN)
lua_islightuserdata(L, n) => (lua_type(L, n) == LUA_TLIGHTUSERDATA)
lua_isnumber(L, n) => (lua_type(L, n) == LUA_TNUMBER)
lua_isstring(L, n) => (lua_type(L, n) == LUA_TSTRING)
lua_istable(L, n) => (lua_type(L, n) == LUA_TTABLE)
lua_isfunction(L, n) => (lua_type(L, n) == LUA_TFUNCTION)
lua_isuserdata(L, n) => (lua_type(L, n) == LUA_TUSERDATA)
lua_isthread(L, n) => (lua_type(L, n) == LUA_TTHREAD)
lua_isnone(L, n) => (lua_type(L, n) == LUA_TNONE)
lua_isnoneornil(L,  n) => (lua_type(L, n) <= 0)
lua_pushglobaltable(L) => lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
lua_insert(L, idx) => lua_rotate(L, idx, 1)
lua_remove(L, idx) => (lua_rotate(L, idx, -1), lua_pop(L, 1))
lua_replace(L, idx) => (lua_copy(L, -1, idx), lua_pop(L, 1))
lua_upvalueindex(i) => (LUA_REGISTRYINDEX - (i))
But that's not all, here's an example translated directly from C from Lua's website itself (with some added stuff for AHK's sake)!

Code: Select all

; =============================================================================
; Example Name             : Lua C API
; Description              : A simple example of how to use Lua's C API.
; Last Tested AHK Version  : v2.0.6
; =============================================================================

#Requires AutoHotkey v2.0
#SingleInstance Force

; ========== ## MODIFY ME ## ==========
#DllLoad *i lua54.dll
; =====================================

; This is to include lua.ahk whether it be in the current directory,
; the lib folder of the current directory, or the AutoHotkey lib directory.
#include <lua>

; Some helpful "C-Styled" print and other debug functions.
print(txt) => fprintf("*", txt)
println(txt) => fprintf("*", txt . "`n")
printf(args*) => fprintf("*", args*)
fprintf(file, frmt, args*) => FileAppend(Format(frmt, args*), file)
debug(txt) => OutputDebug(txt)
input() => RTrim(STDIN.ReadLine(), "`n")

DllCall("AttachConsole", "int", -1, "int")
DllCall("AllocConsole")

global STDOUT := DllCall("GetStdHandle", "UInt", -11, "UPtr")
global STDERR := DllCall("GetStdHandle", "UInt", -12, "UPtr")
global STDIN := FileOpen("*", "r")

; Our entry point.
main(args*)
{
	; All Lua contexts are held in this structure. We work with it almost
	; all the time.
	L := luaL_newstate()

	; Load Lua libraries
	luaL_openlibs(L)

	; Load the file containing the script we are going to run
	status := luaL_loadfile(L, "script.lua")
	if (status != LUA_OK)  ; There was an issue...
	{
		; If something went wrong, error message is at the top of the stack,
		; So let's print it to the standard error output;
		fprintf(STDERR, "Couldn't load file: {1}`n", lua_tostring(L, -1))
		; And return 1 as in `EXIT_FAILURE` in C.
		return 1
	}

	/*
		Ok, now here we go: We pass data to the lua script on the stack.
		That is, we first have to prepare Lua's virtual stack the way we
		want the script to receive it, then ask Lua to run it.
	*/
	lua_newtable(L)  ; First, we will pass a table

	/*
		To put values into the table, we first push the index, then the
		value, and then call lua_rawset() with the index of the table in the
		stack. Let's see why it's -3: In Lua, the value -1 always refers to
		the top of the stack. When you create the table with lua_newtable(),
		the table gets pushed into the top of the stack. When you push the
		index and then the cell value, the stack looks like:

		<- [stack bottom] -- table, index, value [top]

		So the -1 will refer to the cell value, thus -3 is used to refer to
		the table itself. Note that lua_rawset() pops the two last elements
		of the stack, so that after it has been called, the table is at the
		top of the stack.
	*/
	loop 5
	{
		lua_pushnumber(L, A_Index)      ; Push the table index
		lua_pushstring(L, A_Index * 2)  ; Push the cell value
		lua_rawset(L, -3)               ; Stores the pair in the table
	}

	; By what name is the script going to reference our table?
	lua_setglobal(L, "foo")

	/*
		Because Lua uses a different standard output, we have to use our own.
		So let's replace the existing print function with our own.
		For this to work, we need to use CallbackCreate() for external DLL functions,
		We cannot simply provide the function itself.
	*/
	lua_register(L, "print", CallbackCreate(lua_print,, 1))

	; Ask Lua to run our little script.
	result := lua_pcall(L, 0, LUA_MULTRET, 0)
	if (result != LUA_OK)
	{
		; If something went wrong, error message is at the top of the stack,
		; So let's print it to the standard error output;
		fprintf(STDERR, "Failed to run script: {1:s}`n", lua_tostring(L, -1))
		; And return 1 as in `EXIT_FAILURE` in C.
		return 1
	}

	; Get the returned value at the top of the stack (index -1)
	sum := lua_tonumber(L, -1)

	; Print what the script returned
	printf("Script returned: {1:.0f}`n", sum)

	lua_pop(L, 1)  ; Take the returned value out of the stack
	lua_close(L)   ; Cya, Lua

	; Close our script with `EXIT_SUCCESS`
	return 0
}

; Our custom print function for Lua.
; lua:  nil print(...)
lua_print(L)
{
	; First we must know how many parameters were passed to our function.
	argc := lua_gettop(L)

	; Then, we must gather those parameters' contents.
	msg := ""
	loop argc
		msg .= lua_tostring(L, A_Index) . "`t"

	; Then we can print our message.
	println(msg)
}

; Startup, similar to pythons `if __name__ == __main__`
if (A_LineFile == A_ScriptFullPath)
{
	ExitApp(main(A_Args*))
}
I'm not a wizard with DLL stuff, so if something doesn't work, you'll have to mess around with it yourself for a while.
If you're stumped on something, or just need help with Lua's C API in general, here's a few links:
Lua manual for 5.4
Lua source code for 5.4
A massive table of Lua's source code, external functions and other interal mumbo jumbo

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

lkwryA
Posts: 1
Joined: 05 Nov 2016, 08:19

Re: Extend Your Scripts/Programs with Lua!

23 Dec 2023, 08:20

Bro, you made my day!
In the past i went thru a lot of discussions on the topic "why use Lua for AHK instead of AHK lang itself".
Main reasons i would like to suggest why:
* AHK lang is a niche used nowhere beside AHK. Lua is less niche.
* Many people (like me) just can not manage, learn, apply a few languages at GOOD level.
Due to, call it limited brain capabilities, or limited time - both does not matter too much.

I personally, if i do not practise a certain languages, after a few months i can hardly recall a half of the language.
If i practice Lua for whatever scripting, i at least remember and can handle the lang.
If i had switch to AHK lang, just forgot half (and going on) of Lua.
Vice versa is true of course.
But there is a difference, Lua can be used for other realms of scripting, while AHK is not.

My personal wishes are:
what abt classic LuaJit v2.1, or Lua v5.1 support ?
These Lua versions are not "time-sensitive", a rough estimation is that half of the Lua market is Luajit+Lua5.1, and that is not going to change
User avatar
Delta Pythagorean
Posts: 628
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: Extend Your Scripts/Programs with Lua!

31 Dec 2023, 15:33

lkwryA wrote:
23 Dec 2023, 08:20
My personal wishes are:
what abt [...] Lua v5.1 support ?
Why? Something wrong with 5.4? Never understood why people want to downgrade a language.

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

vmech
Posts: 469
Joined: 25 Aug 2019, 13:03

Re: Extend Your Scripts/Programs with Lua!

12 Feb 2024, 04:56

@Delta Pythagorean
Why you use

Code: Select all

result == 0 ? "" : StrGet(result, "UTF-8")
instead of

Code: Select all

result ? StrGet(result, "UTF-8") : ""
?
Please post your script code inside [code] ... [/code] block. Thank you.
User avatar
Delta Pythagorean
Posts: 628
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: Extend Your Scripts/Programs with Lua!

12 Feb 2024, 21:48

@vmech
I wanted to make it clear that it returned NULL as null in C is a void pointer, pointing to 0.

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

k0stell0
Posts: 21
Joined: 30 Oct 2022, 18:01

Re: Extend Your Scripts/Programs with Lua!

21 Mar 2024, 19:43

Hi @Delta Pythagorean,

Thanks a lot for sharing your work.
I wonder if you can help with the following.
I have some Lua scrips for Davinci Resolve, and I also use Taran Van Hemert's macro keyboard, which is basically AHK v2 script.
So, I'm trying to execute those Lua scripts by pressing a key on that macro keyboard, but due to lack of experience, I wasn't successful in doing so.

Is that something possible to do using your Lua.ahk?

Thanks in advance.
User avatar
Delta Pythagorean
Posts: 628
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: Extend Your Scripts/Programs with Lua!

23 Mar 2024, 03:57

@k0stell0
The request of yours doesn't match the usage of my library, in fact your request is more akin to the default nature of AHK. I recommend looking at the tutorials here.

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat


Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: Spikea and 15 guests