LuaNT 4.0

Page created by RedstoneShell (LuaNT author and main developer)

WARNING: Is a external OS, it's not official OS in OpenComputers

Another Operation System for OpenComputers. Yet, he don't have own Floppy. Possibly first OS with GUI that can run at Tier 2 computers, also perfectly run at Tier 3. Can start at 2x Tier 1 Memory (384KB), but for stable work recomended 2x Tier 2 Memory (768KB). Also can run at 1MB HDD Disk, or 512KB Floppy.

Showcase

For install you need computer with installed OpenOS and installed Internet Card. Before installation, place in HDD slot second Hard Disk Drive, for economy place 1MB, but you can place other. After installing HDD, run in console “pastebin get -f Kv80QkZG luantinstall.lua”, after successful installation of script, enter “luantinstall”. After that follow steps, and at disk selection enter number of second HDD, if you don't know what HDD you install, just select number of HDD that don't named “openos” or “tmpfs”.

OR IF YOU NEED MANY LuaNT's, install standard Floppy Disk and select it. Now at Desktop you can open menu of My Pc and click Install To... for install at network of computers!

After installation in Reboot option enter NO, and shutdown computer. After shutdowning, remove HDD with OpenOS, and left only LuaNT disk.

Now start computer, if you made this correctly at monitor you see something this

Don't worry if after “BCD: Parsing Boot Configuration Data entries…”, system don't make anything, she just initialize without Debug Log at screen.

After seeing Log On menu, press Enter, and enter in password field: “admin123”. After Enter, if you enter correctly, you see desktop.

from 14.09.2026 you can switch Tier 2 Compatiblity Mode, here's how it changes screen:

And without Tier 2 CM:

First work

If you initialize LuaNT 4.0 at Tier 3 computer, everything is okay. But if you have some Tier 2 computer (“bucket”), some colors can be weird… for that, click at Start, and select Color Setup…, now click at 4 bit colors, click OK, and reboot to see changes. For better GUI's at Tier 2, also click in WinLogOn in left-top corner button named T2 Mode, after clicking computer reboot in Tier 2 Compatible Mode.

Yes, LuaNT 4.0, making for Powerful Tier 3 computers, but from 4.0.1.3 she start some adaptation for Tier 2 computers, for 4.0.1.3 only maked Color Setup…

Now return to work. At desktop you can see My PC, SMS, Minesweeper, Internet Explorer, Setup Mgr and Notepad. If you need to customize your LuaNT copy, open Notepad and use Ctrl+O to open file, enter file to open, write data,and click to X at right-top corner of Notepad and Save file. For other customizations, open SMS, select need package, press Enter, read description, press Enter for install, and run.

Also by open menu at My PC, you can see Install To… that unclickable at HDD. and Properties, by clicking at Properties, opens this window:

LuaNT is a modular OS, that means, she doesn't have “everything from box”, you install needed modules from SMS, or use standard 490KB LuaNT from installer.

Read more about OS here:

LuaNT Main Page, and help

Getting Started Manuals and FAQ's

Some note: If you need to start DHCP, check if you have 1MB+ RAM, or he don't launch!

Interesting fact! You can launch LuaNT 4.0 from Floppy!

From 14.09.2026, you can use program nfs.lua(Network FileSystem), install by SMS > Wireless HDD.

For start, create Server/Computer with OpenOS, insert disk with OpenOS, and disk that need to make Wireless, or connect external RAID, he identical. Now install NetHDD Server→"pastebin get -f zNjJJFZ3 nethdd_serv.lua", and enter "nethdd_serv". If you start it at first time, he displays HDD's list to select HDD's to make it Wireless, enter number of needed disk and press ENTER. After that server makes this disk Wireless.

Now at LuaNT open Start menu > Run…, and enter “nfs.lua” and press ENTER. Wait some time and in window click “[SEARCH]”, wait some time, and click enter at needed server (TIP: edit nethdd_serv.lua to change server name from “\\\\MYSERVER\\disk” to needed). Now after ENTER, open new windows, by clicking any letter at keyboard, you set letter of Wireless disk, by press “[OK]” you mount wireless disk in system. Now you can open Start menu > File Explorer, and select new disk that named for example: “\\MYSERVER\disk”.

Now after mounting, every programs and LuaNT see this disk as normal disk in your computer. WARNING!: After reboot, connection to disk RESET, after that you need to repeat all actions to Re-mount Wireless back in LuaNT!

For example you can watch this video about launching and setup Wireless HDD

From 15.09.2026, you can use program L2ctrl.lua(Relay Controller), install by SMS > L2Control. For start, place near Relay and connect to computer with LuaNT, now open Start menu > Run… and enter “L2ctrl” and press ENTER, in menu you can switch Repeater mode, for wireless Relay switch strenght.

For Lossmeter button, create computer with OpenOS, place Network Card, run “pastebin get -f VebVX21x lossmeter.lua” and start “lossmeter”.

After connect OpenOS computer to Relay by Cable, now check if in computer with LuaNT installed Network Card, after check click button Lossmeter, wait some time, and after that at screen you see % of Loss, transmited/received packets and lossed packets. Heres menu:

Loss for Relay without anything:

Loss for Relay with Tier 2 CPU only:

Loss for Relay with Tier 3 CPU only:

from 26.09.2026 you can use “eToken” in LuaNT, for make 2FA WinLogOn.

Before that PLEASE, check if you have external Disk Drive for PC or internal Disk Drive item for Server Rack. First, you need to make your eToken, for that install SMS > eTokenSetup. After click Start > Run…, enter etoken and ENTER. At opened menu click [ Create Token ]:

Now in new menu enter PIN-code for token, WARNING!: Please remember the entered PIN-code in menu, to Logon in system!

After entering PIN, press ENTER, and close program.

Now install SMS > eTokenLogOn, after that, reboot PC, now you can see new WinLogOn, for start, place in external Disk Driver your eToken floppy, press ENTER in WinLogOn, after enter PIN-code that you enter in eTokenSetup. After entering PIN-code, you see normal Logon menu.

New LogOn menu:

PIN Code:

API

Now, if you developer, that customize system or just script maker, make sure, this API is very different that MineOS or OpenOS, heres everything that you can use:

  • DbgPrint(msg:text)

    Save message in log file “/ntbootdd.log” message, also with _G.DebugMode=true, displays message at screen

  • _G.DbgPrintToFile:boolean
    Access to write messages in file, standard is “true”, change his value in /init.lua to disable log writing, or set it to “false” anywhere in program, to stop logging every DbgPrint's from this point
  • _G.DispatcherActive:boolean
    WARNING!: Changing of this varible, immedantly shutdown LuaNT multi-task, and active thread before "coroutine.yield()" collect every proccessor time!. Switch for disabling multi-task, use at own risk!
  • _G.Drives:table
    Table of every registered filesystem's. Format {["C:"="\Device\<name><number>\Partition<number>"], ... }. Reading of this Kernel Table, is safe. But if you need to change values in this table, don't manipulate with registered Devices, you can add your RAM Disks, or other…, if you need to change exists Device in table, change values at own risk! Removing disks from table, makes OS lost every information about this Drive (you cannot find removed letter anymore in File Explorer, attempt to make something with this letter return error), before computer restart.
  • _G.InThreadResume:boolean
    Value used only in Kernel Multi-task engine. If true, core skip update of current thread. Change at own risk.
  • _G.Mm.NonPagedPool:table
    Every information about system stores here: drivers, disks, floppies, devices, services. Use value of _G.Drives["<disk_letter>:"] here to get link to disk filesystem component object. You can add here everything, but change exists value at own risk. Changing of critical structures can cause a BSOD. If you get from this table, filesystem for disk, you can check if this disk is Wireless, Wireless HDD have identical filesystem but with addtive parameters is: "_isNetwork:boolean" - true if HDD is Remote, don't exists in normal filesystem component of HDD's, "_server" - name of this disk for every Clients with nfs.lua, set only at server that provide connection to disk over Wireless Network Card
  • _G.NTTC:integer
    Counter of Kernel Main Loop ticks. Add 1 every ~0.01-0.06s.
  • _G.ThreadGC:table
    Main Garbage Collector settings. About GC: This is a custom GC in kernel to clean dead threads. Have this values: "Enabled:boolean" - If true thread with many errors, can be downed by priority. "Interval:integer" - Interval, if Counter⇒Interval, kernel start GC to find dead tasks to clean, "Counter:integer" - You know what im do. Changing of this parameters only change intervals to preventing memory leaks. Making this table nil, cause immedantly BSOD if core try to check dead threads.
  • _G.PspActiveProcessList:table
    Active processes list. Here core store every information about active processes in multi-task. Changing of this table incorrectly can cause unstable work of LuaNT or BSOD on change critical values.
  • LdrLoadDll(path:string):table|nil, string
    Is a analog of require, for LuaNT. Returns library as first argument, addtive have second return val, is a error, if first args is nil, second is error
  • KeDelayExecutionThread(seconds:integer)
    Halt current thread at time received in seconds.
  • PsCreateSystemThread(path:string, name:string, priorityClass:integer, token:table):table|boolean
    Open file received in path, register it in Multitask with name received at name parameter. Also, for priorityClass use this table: HIGH_PRIORITY = 31, REALTIME_PRIORITY = 16, NORMAL_PRIORITY = 8, IDLE_PRIORITY = 0 or make nil for automatic priority (NORMALPRIORITY). If you hate token just make it nil, for register thread as SYSTEM, for other receive in parameter this structure: { name = "name_of_token", group = "token_group" }. Returns false if thread don't created, returns Thread, if created.
    - PsTerminateThread(pid:integer):boolean
    Terminates and remove task from Task Scheduler. For removal, search needed thread PID, and enter in this function. Returns true if removed, false if error.
    - PsSetPriority(pid:integer, newPriority:integer):boolean
    Change priority of selected Thread by this PID. in newPriority set number from PsCreateSystemThread priority table. Returns true if success, false if error.
    - KiCleanDeadThreads():integer
    WARNING!: Is Kernel function, calling anywhere external can make LuaNT work unstable. This function works only in LuaNT Kernel. This function used in LuaNT Kernel Garbage Collector. This GC collect stopped/crashed threads and remove it. This function search in Task Scheduler every dead threads, and mark it, after search, every dead threads cleaning. Returns a number of cleaned threads.
    - PerformSystemShutdown()
    WARNING!: This function shutdown LuaNT, all unsaved data automaticaly lossed after calling! Shutdown LuaNT.
    - _G.HAL.HalQueryRealTimeClock(PTIME_FIELDS:table)
    In first arg you need to receive empty table: local tabl={}, after function, table contains Year, Month, Day, Hour, Minute, Second, Milliseconds, Weekday.
    - GDI.PatBlt(hdc:table, x:int, y:int, w:int, h:int, dwRop:int):boolean
    From module gdi32.lua. In hdc write GDI.GetDC(0). dwRop is operation that GDI32+ need to make, here list: GDI.PATCOPY paint destination rectangle by Brush Color, GDI.BLACKNESS/WHITENESS makes color of rectangle 0x000000/0xFFFFFF, GDI.PATINVERT makes color of rectangle a inverted from Bursh Color.
    - GDI.CreateSolidBrush(clr:int): table
    Makes Brush for PatBlt, in clr you receive 0xRRGGBB. Return object use in SelectObject
    - GDI.SelectObject(hdc_:table, hObj:table): tabke
    Receive in hdc
    GDI.GetDC(0). in hObj receive needed object. Returns integer, for brush, he returns old brush
  • GDI.SetTextColor(hdc:table, color:integer): integer
    Receive in hdc GDI.GetDC(0). in color receive needed color in 0xRRGGBB. Returns previous color.
  • GDI.TextOut(hdc:table, x:integer, y:integer, text:string)
    Receive in hdc GDI.GetDC(0).
  • wininet.HttpGet(url:string):boolean, string
    From module wininet.lua. Returns in first arg true at success, false at error. In second args returns error, or content.
  • winprint.PrintText(text:string, alignment:string, color:integer):boolean, string
    From module winprint.lua. WARNING!: For working, need add-on OpenPrinter, and connected Printer to computer! In text receive needed text for print, separate by \r\n. In alignment receive “center”, “left” or “right. In color 0xRRGGBB. Returns true on success, false on error and error text in next arg.
  • _G.RpcSs.RpcServerRegisterIf(interfaceName:string, methodsTable:table):boolean
    Register in global RPC your program callbacks. In interfaceName enter name of your interface, in second args receive table, example {a=function(a,b) return a+b end}
  • _G.RpcSs.RpcCliExecute(interfaceName:string, methodName:string, ...):boolean, string|object

    Call method from registered interface. In ... you receive arguments for function. Returns true on success, false on error. At true in second args returns function result, at false in second args text of error.

  • _G.RpcSs.RpcServerUnregisterIf(interfaceName:string):boolean
    Unregister interface by name in first arg. Returns true on success, false on error.
  • esent.JetInit():boolean
    From module esent.lua. Initialize instance (64 pages x 4096 bytes=256KB)
  • esent.SetCatalog(catalog:table):boolean, string
    Change standard DHCP catalog to received in first arg. Call after JetInit but before JetOpenDatabase. Returns true if success, false if instance don't initialized.
  • esent.JetBeginSession():integer
    Opens new session for working with ESENT database. Returns sessionId for using in other functions.
  • esent.JetOpenDatabase(sesid:integer, path:string):boolean, string
    Open database for read/write. In first arg you receive sssionId from JetBeginSession, in second arg you receive path to create/load DB file. Returns true at success, false on error, error text in second return arg.
  • esent.JetPrepareUpdate(sesid:integer, tableName:string, prepType:string):boolean, string
    Prepare operation for Jet Blue. In first arg you receive result of JetBeginSession, in second arg you receive name of table, WARNING!: The table name must exist in the Catalog., in third arg you receive operation for page: "Insert", "Update" or "Delete". Returns true if success, false if error.
  • esent.JetSetColumn(sesid:integer, columnName:string, value:object):boolean, string
    Set in column value. WARNING!: Before call, check if before this you calll JetPrepareUpdate. In first arg you receive result of JetBeginSession. In second arg name of column that write in page buffer. In third arg you receive value that set in column. Returns true if success, false if error.
  • esent.JetUpdate(sesid:integer):boolean
    Updates page. WARNING!: Function locked at DHCP Tables..
  • esent.JetBeginTransaction(sesid:integer):boolean, string
    Start new transaction. Make savepoint with snapshot of every pages. In first args you receive result of JetBeginSession. Returns true if success, false if error.
  • esent.JetCommitTransaction(sesid:integer, grbit:object);boolean, string
    Record active transaction and increase checkpoint. In first args you receive result of JetBeginSession, second arg unused. Returns true if success, false if error.
  • esent.JetRollback(sesid:integer):boolean, string
    Rollback pages to last savepoint, use at own risk, every data after last savepoint remove. In first args you receive result of JetBeginSession. Returns true if success, false if error.
  • esent.JetOpenTable(sesid:integer, tableName:string):boolean|integer, string
    Open new cursor for this page. In first args you receive result of JetBeginSession. In second arg you receive name of table WARNING!: The table name must exist in the Catalog.. Returns cursorId on success, false if error.
  • esent.JetMove(cursorId:integer, direction:string|integer):boolean
    Move opened cursor. In first arg you receive ID of opened cursor by JetOpenTable. In second arg you receive command for cursor: "Next" or 1 move cursor to next column, "Prev" or -1 moves cursor to previous column, "First"/"Last" move cursor to first/last column. Return true on success move, false if cursor at start/end of page.
  • esent.JetRetrieveColumn(sesid:integer, cursorId:integer, columnName:string):nil|string
    Read value of column by cursor. In first args you receive result of JetBeginSession, in second arg you receive ID of opened cursor by JetOpenTable. In third arg you receive name of column in page. Returns value of this column, nil if error.
  • esent.JetCloseTable(cursorId:integer):boolean
    Closes cursor. In first arg you receive ID of opened cursor by JetOpenTable. Returns true if success, false on error.
  • esent.QueryRecords(tableName:string):table
    Returns all records in table as lines array, returns empty table on error. In first arg you receive name of table WARNING!: The table name must exist in the Catalog.
  • esent.DeleteRecord(tableName:string, keyColumn:string, keyValue:object):boolean
    Delete record in column in page, delete value if value of column equals to keyValue. In first arg you receive name of table WARNING!: The table name must exist in the Catalog., in second args you receive column name in page, in third arg you receive value, if value equal to value in column, record deletes. Returns true if success, false on error.
  • esent.JetCloseDatabase(sesid:integer, path:string):boolean, string
    Close database. In first arg you receive sssionId from JetBeginSession, in second arg you receive path to DB file. Returns true if success, false on error.
  • esent.JetEndSession(sesid:integer):boolean
    Close opened session. In first arg you receive sssionId from JetBeginSession. Returns true if success, false if error.
  • esent.JetTerm():boolean
    End work and reset status. Use only after correct database close. Returns true if success, false if error.
  • _G.IoConnectInterrupt(ServiceRoutine:function):string|nil
    Register interrupt handler in kernel. In first arg you enter function that parse interrupt, here signature: ServiceRoutine(struct:table, sig:string, addr:string, arg1:object, arg2:object, arg3:object, arg4:object), ignore first param, sig is name of event (“key_down”, “touch”, …), arg1..4 is arguments of specified event (See Also: Signals). Returns uniquie id of handler in format IRQ%08X for example: IRQ3EF3DEE9, you need to save this code, for disconnect your handler later. Returns nil if ServiceRoutine is not a function. Since: 4.0.1.4
  • _G.IoDisconnectInterrupt(InterruptObject:string):boolean
    Disconnect interrupt handler by his ID. In first arg you receive saved IRQ%08X from IoConnectInterrupt. Returns true if handler found and deleted. false if don't found of first args is not string. Since: 4.0.1.4
  • _G.KeInitializeEvent(Event:table[, Type:integer, State:boolean])
    Initialize event object. If first arg you receive table that init as event, if second arg you receive event type (0=NotificationEvent - every waiting wake up, 1=SynchronizationEvent - only one waiting wake up), in third arg you receive initial state (true - signaled, false - not signaled). Don't return's anything, only fill empty table at input by values. Since: 4.0.1.4
  • _G.KeSetEvent(Event:table[, Increment:integer, Wait:boolean]):boolean
    Set event in signaled state and wake up everyone that waiting. In first arg you receive table filled by KeInitializeEvent. Returns true if event set, false if input is nil. Since: 4.0.1.4
  • _G.ExInitializeWorkItem(WorkItem:table, Routine:function, Context:object)
    Initializes a Work Item for subsequent queuing. In first arg you receive empty table for filling, in second arg you receive function that call at Work Item processing, singnature: Routine(Context), if third arg you receive anything that received in Routine. Since: 4.0.1.4
  • _G.ExQueueWorkItem(WorkItem:table, QueueType:integer):boolean
    Set Work Item in system query for executing. In first arg you receive initialized WorkItem by ExInitializeWorkItem. Second arg don't make anything. Return true if Work Item added in query, false if Work Item invalid or in query. Since: 4.0.1.4
  • _G.ExDequeueWorkItem():table|nil
    WARNING!: Calling of this function external can cause unstable work of PnP and other drivers/programs that use System Query!. Calling inside of kernel main loop, get second Work Item from query, delete and return 1st element in query. Returns table with second Work Item from query, nil if System Query don't exists or nil. Since: 4.0.1.4

Examples

Example of worker with JetBlue

snippet.lua
local esent, err = LdrLoadDll("Windows/System32/esent.lua")
if not esent then
    DbgPrint("TEST: Cannot load esent.lua: " .. tostring(err))
    return
end
esent.JetInit()
esent.SetCatalog({
    ["MyTable"] = {
        columns = { "ID", "Name", "Value" },
        rootPage = 3
    }
})
DbgPrint("TEST: Catalog replaced with MyTable (rootPage=3)")
local sesid = esent.JetBeginSession()
DbgPrint("TEST: Session created, sesid=" .. tostring(sesid))
local dbPath = "/mydb.dat"
esent.JetOpenDatabase(sesid, dbPath)
DbgPrint("TEST: DB open: " .. dbPath)
esent.JetPrepareUpdate(sesid, "MyTable", "Insert")
esent.JetSetColumn(sesid, "ID", "1")
esent.JetSetColumn(sesid, "Name", "TestRecord")
esent.JetSetColumn(sesid, "Value", "Hello LuaNT")
esent.JetUpdate(sesid)
DbgPrint("TEST: Record added to MyTable")
local records = esent.QueryRecords("MyTable")
DbgPrint("TEST: Records in MyTable: " .. tostring(#records))
for i = 1, #records do
    DbgPrint("TEST: Record " .. i .. ": " .. records[i])
end
esent.JetCloseDatabase(sesid, dbPath)
DbgPrint("TEST: DB closed")
esent.JetEndSession(sesid)
esent.JetTerm()

Copy this code, and write in Windows/test.lua. After that open Start menu > Run… and enter test.lua. If you make everything correctly, computer make some noise 4-7s. And in logs you can see:

snippet.text
[1970-01-18 23:00:14] RUN: Starting Windows/test.lua
[1970-01-18 23:00:18] KE: Created Thread 'test' at Priority 8
[1970-01-18 23:04:08] ESENT: JetInit - Instance initialized with 64 pages (256.0 KB)
[1970-01-18 23:04:08] TEST: Catalog replaced with MyTable (rootPage=3)
[1970-01-18 23:04:12] ESENT: JetBeginSession - Session 1 created
[1970-01-18 23:04:12] TEST: Session created, sesid=1
[1970-01-18 23:04:15] ESENT: JetOpenDatabase - New database will be created at /mydb.dat
[1970-01-18 23:04:15] ESENT: JetOpenDatabase - Database '/mydb.dat' opened
[1970-01-18 23:04:19] TEST: DB open: /mydb.dat
[1970-01-18 23:04:19] ESENT: JetPrepareUpdate - Prepared Insert for table MyTable
[1970-01-18 23:04:22] ESENT: JetUpdate - Inserted record into MyTable
[1970-01-18 23:04:22] TEST: Record added to MyTable
[1970-01-18 23:04:26] TEST: Records in MyTable: 1
[1970-01-18 23:04:26] TEST: Record 1: ID=1;Name=TestRecord;Value=Hello LuaNT;
[1970-01-18 23:05:45] ESENT: JetCloseDatabase - Wrote 64 pages to /mydb.dat
[1970-01-18 23:05:49] TEST: DB closed
[1970-01-18 23:05:49] ESENT: JetEndSession - Session 1 ended
[1970-01-18 23:05:52] ESENT: JetTerm - Instance terminated
[1970-01-18 23:05:52] KE: Thread test (PID: 5) finished normally.
[1970-01-18 23:09:43] KE: Thread test (PID: 5) has finished normally.
[1970-01-18 23:09:43] KE: GC - Cleaning dead thread test (PID: 5)
[1970-01-18 23:09:46] MM: Freeing resources for Thread: test
[1970-01-18 23:09:46] KE: GC cleaned 1 dead threads

You can see result by downloading ESEDBView from SMS, open he by Start menu > Run… and enter “esedbview.lua”, press ENTER. Follow instructions, and you can se this

Also you can try this GDI32+ example:

snippet.lua
local gdi32, err = _G.KRNL_GDI32 or LdrLoadDll("Windows/System32/gdi32.lua")
if not gdi32 then
    DbgPrint("GDI_TEST: Cannot load gdi32.lua: " .. tostring(err))
    return
end
local hdc = gdi32.GetDC(0)
if not hdc then
    DbgPrint("GDI_TEST: GetDC(0) return nil")
    return
end
local screenW = _G.HAL.w
local screenH = _G.HAL.h
local boxW = 20
local boxH = 10
local boxX = math.floor((screenW - boxW) / 2)
local boxY = math.floor((screenH - boxH) / 2)
local redBrush = gdi32.CreateSolidBrush(0xFF0000)
gdi32.SelectObject(hdc, redBrush)
gdi32.PatBlt(hdc, boxX, boxY, boxW, boxH, gdi32.PATCOPY)
local whiteBrush = gdi32.CreateSolidBrush(0xFFFFFF)
gdi32.SelectObject(hdc, whiteBrush)
gdi32.PatBlt(hdc, boxX, boxY, boxW, 1, gdi32.PATCOPY)
gdi32.PatBlt(hdc, boxX, boxY + boxH - 1, boxW, 1, gdi32.PATCOPY)
gdi32.PatBlt(hdc, boxX, boxY, 1, boxH, gdi32.PATCOPY)
gdi32.PatBlt(hdc, boxX + boxW - 1, boxY, 1, boxH, gdi32.PATCOPY)
gdi32.SetTextColor(hdc, 0x000000)
gdi32.SetBkColor(hdc, 0xFF0000)
local label = "GDI+"
local textX = boxX + math.floor((boxW - #label) / 2)
local textY = boxY + math.floor(boxH / 2)
gdi32.TextOut(hdc, textX, textY, label)
while true do
    local signal = { computer.pullSignal(1) }
    local event = signal[1]
    if event == "key_down" or event == "touch" then
        break
    end
end

After start he draw this

Also you can try new 4.0.1.4 features by this example driver:

snippet.lua
local demo = {}
demo.InterruptObject = nil
function demo.OnInterrupt(struct, sig, addr, arg1, arg2, arg3, arg4)
    if sig == "key_down" then
        _G.DbgPrint("[DemoDriver] key_down: char=" .. tostring(arg1) ..
            " code=" .. tostring(arg2))
    elseif sig == "touch" then
        _G.DbgPrint("[DemoDriver] touch: x=" .. tostring(arg1) ..
            " y=" .. tostring(arg2))
    elseif sig == "component_added" then
        _G.DbgPrint("[DemoDriver] component_added: addr=" .. tostring(addr))
    elseif sig == "component_removed" then
        _G.DbgPrint("[DemoDriver] component_removed: addr=" .. tostring(addr))
    end
end
function demo.DriverEntry()
    _G.DbgPrint("[DemoDriver] Loading...")
    demo.InterruptObject = _G.IoConnectInterrupt(demo.OnInterrupt)
    if demo.InterruptObject then
        _G.DbgPrint("[DemoDriver] Registered interrupt at \\Structures\\" ..
            demo.InterruptObject)
    else
        _G.DbgPrint("[DemoDriver] FAILED to register interrupt")
        return nil
    end
    if _G.RegisterShutdownDriver then
        _G.RegisterShutdownDriver("demodriver", demo)
    end
    _G.DbgPrint("[DemoDriver] Loaded successfully")
    return demo
end
function demo.DriverUnload()
    _G.DbgPrint("[DemoDriver] Unloading...")
    if demo.InterruptObject and _G.IoDisconnectInterrupt then
        _G.IoDisconnectInterrupt(demo.InterruptObject)
        _G.DbgPrint("[DemoDriver] Disconnected interrupt " .. demo.InterruptObject)
        demo.InterruptObject = nil
    end
    _G.DbgPrint("[DemoDriver] Unloaded")
    return true
end
return demo.DriverEntry()

After start, if remove everything from log, you can see that:

snippet.text
[1970-01-31 00:39:25] ntoskrnl: Loading driver \SystemRoot\System32\drivers\demo.lua
[1970-01-31 00:39:28] [DemoDriver] Loading...
[1970-01-31 00:39:32] IO: IoConnectInterrupt - Registered handler at \Structures\IRQ3374CDC2
[1970-01-31 00:39:32] [DemoDriver] Registered interrupt at \Structures\IRQ3374CDC2
[1970-01-31 00:39:36] [DemoDriver] Loaded successfully
[1970-01-31 00:39:36] ntoskrnl: Driver demo loaded successfully
...
[1970-01-31 00:54:10] [DemoDriver] key_down: char=100 code=32
[1970-01-31 00:54:46] [DemoDriver] key_down: char=109 code=50
[1970-01-31 00:55:04] [DemoDriver] key_down: char=105 code=23
[1970-01-31 00:55:22] [DemoDriver] key_down: char=110 code=49
[1970-01-31 00:55:55] [DemoDriver] key_down: char=49 code=2
[1970-01-31 00:56:13] [DemoDriver] key_down: char=50 code=3
[1970-01-31 00:56:27] [DemoDriver] key_down: char=51 code=4
...
[1970-01-31 00:58:30] [DemoDriver] key_down: char=13 code=28
[1970-01-31 00:59:56] [DemoDriver] touch: x=71 y=20
[1970-01-31 01:01:55] [DemoDriver] touch: x=56 y=30
...
[1970-01-31 01:04:04] [DemoDriver] touch: x=4 y=48
...
[1970-01-31 01:04:19] [DemoDriver] touch: x=4 y=48
...
[1970-01-31 01:05:42] [DemoDriver] touch: x=10 y=41
...

Also you can combine Io with Ex:

snippet.lua
local demo = {}
demo.InterruptObject = nil
demo.WorkItem = {}
demo.Counter = 0
local function ProcessSignal(Context)
    local sig = Context.sig
    local arg1 = Context.arg1
    local arg2 = Context.arg2
    demo.Counter = demo.Counter + 1
    _G.DbgPrint(string.format(
        "[DemoWorkItem] #%d Processing signal: sig=%s arg1=%s arg2=%s",
        demo.Counter, tostring(sig), tostring(arg1), tostring(arg2)
    ))
    if sig == "key_down" then
        _G.DbgPrint("[DemoWorkItem] Key pressed: " .. tostring(arg1))
    elseif sig == "touch" then
        _G.DbgPrint("[DemoWorkItem] Touch at: " .. tostring(arg1) .. "," .. tostring(arg2))
    elseif sig == "component_added" then
        _G.DbgPrint("[DemoWorkItem] Component arrived")
    elseif sig == "component_removed" then
        _G.DbgPrint("[DemoWorkItem] Component removed")
    end
    Context.sig = nil
    Context.arg1 = nil
    Context.arg2 = nil
end
 
function demo.OnInterrupt(struct, sig, addr, arg1, arg2, arg3, arg4)
    if sig ~= "key_down" and sig ~= "touch"
       and sig ~= "component_added" and sig ~= "component_removed" then
        return
    end
    demo.Context.sig = sig
    demo.Context.arg1 = arg1
    demo.Context.arg2 = arg2
    local ok = _G.ExQueueWorkItem(demo.WorkItem, 1)
    if not ok then
        _G.DbgPrint("[DemoWorkItem] Failed to queue WorkItem (already in queue?)")
    end
end
function demo.DriverEntry()
    _G.DbgPrint("[DemoWorkItem] Loading...")
    demo.Context = {
        sig = nil,
        arg1 = nil,
        arg2 = nil
    }
    _G.ExInitializeWorkItem(demo.WorkItem, ProcessSignal, demo.Context)
    _G.DbgPrint("[DemoWorkItem] WorkItem initialized")
    demo.InterruptObject = _G.IoConnectInterrupt(demo.OnInterrupt)
    if demo.InterruptObject then
        _G.DbgPrint("[DemoWorkItem] Interrupt registered at \\Structures\\" ..
            demo.InterruptObject)
    else
        _G.DbgPrint("[DemoWorkItem] FAILED to register interrupt")
        return nil
    end
    if _G.RegisterShutdownDriver then
        _G.RegisterShutdownDriver("demoworkitem", demo)
    end
    _G.DbgPrint("[DemoWorkItem] Loaded successfully")
    return demo
end
function demo.DriverUnload()
    _G.DbgPrint("[DemoWorkItem] Unloading...")
    if demo.InterruptObject and _G.IoDisconnectInterrupt then
        _G.IoDisconnectInterrupt(demo.InterruptObject)
        demo.InterruptObject = nil
    end
    demo.WorkItem = {}
    demo.Context = nil
    _G.DbgPrint("[DemoWorkItem] Unloaded")
    return true
end
return demo.DriverEntry()

After starting you can see that, if try to login in WinLogon (ENTER→a→d→m→i→n→1→2→3→ENTER):

snippet.text
[1970-01-31 01:38:02] ntoskrnl: Loading driver \SystemRoot\System32\drivers\demo.lua
[1970-01-31 01:38:06] [DemoWorkItem] Loading...
[1970-01-31 01:38:09] [DemoWorkItem] WorkItem initialized
[1970-01-31 01:38:09] IO: IoConnectInterrupt - Registered handler at \Structures\IRQ7E13ACB9
[1970-01-31 01:38:13] [DemoWorkItem] Interrupt registered at \Structures\IRQ7E13ACB9
[1970-01-31 01:38:13] [DemoWorkItem] Loaded successfully
[1970-01-31 01:38:16] ntoskrnl: Driver demo loaded successfully
...
[1970-01-31 01:51:39] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:51:39] [DemoWorkItem] #1 Processing signal: sig=key_down arg1=13 arg2=28
[1970-01-31 01:51:46] [DemoWorkItem] Key pressed: 13
[1970-01-31 01:53:34] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:53:34] [DemoWorkItem] #2 Processing signal: sig=key_down arg1=97 arg2=30
[1970-01-31 01:53:38] [DemoWorkItem] Key pressed: 97
[1970-01-31 01:54:54] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:54:54] [DemoWorkItem] #3 Processing signal: sig=key_down arg1=100 arg2=32
[1970-01-31 01:54:57] [DemoWorkItem] Key pressed: 100
[1970-01-31 01:56:02] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:56:02] [DemoWorkItem] #4 Processing signal: sig=key_down arg1=109 arg2=50
[1970-01-31 01:56:06] [DemoWorkItem] Key pressed: 109
[1970-01-31 01:56:56] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:56:56] [DemoWorkItem] #5 Processing signal: sig=key_down arg1=105 arg2=23
[1970-01-31 01:57:00] [DemoWorkItem] Key pressed: 105
[1970-01-31 01:57:32] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:57:32] [DemoWorkItem] #6 Processing signal: sig=key_down arg1=110 arg2=49
[1970-01-31 01:57:36] [DemoWorkItem] Key pressed: 110
[1970-01-31 01:58:44] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:58:44] [DemoWorkItem] #7 Processing signal: sig=key_down arg1=49 arg2=2
[1970-01-31 01:58:48] [DemoWorkItem] Key pressed: 49
[1970-01-31 01:59:27] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 01:59:27] [DemoWorkItem] #8 Processing signal: sig=key_down arg1=50 arg2=3
[1970-01-31 01:59:31] [DemoWorkItem] Key pressed: 50
[1970-01-31 02:00:18] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 02:00:18] [DemoWorkItem] #9 Processing signal: sig=key_down arg1=51 arg2=4
[1970-01-31 02:00:21] [DemoWorkItem] Key pressed: 51
[1970-01-31 02:01:22] WINLOGON: Auth success, initializing Desktop for user: Administrator
[1970-01-31 02:01:22] RPC: Interface 'IServiceControlManager' successfully registered.
[1970-01-31 02:03:14] IO: ExQueueWorkItem - WorkItem queued (type=1)
[1970-01-31 02:03:14] [DemoWorkItem] #10 Processing signal: sig=key_down arg1=13 arg2=28
[1970-01-31 02:03:18] [DemoWorkItem] Key pressed: 13