Table of Contents

Bindings generation for QT6

In this post I'm covering the compilation of QT6 on linux, and then I continue with more lua bindings on windows: that second part was a lot of fun actually, and I ended up with a working minimal QT window built completely from lua 😁! There is still a lot to do here, but who knows, maybe someday I'm gonig to replace pyQT6 this way ?! 🤣. Just keep reading below if you're interested in some painful errors or brief moment of joy on this journey ;-)

Initial setup on linux

Adding support for tool compilation

Build NSS on linux

Fixing error with version of nodejs

Fixing error with build of QtPdf

Building the test QT app in NervLand

Running the QT test app

Automatic deployment of dependency binaries

This also means that now, I don't really need to keep those external binaries on the repository anymore: they will be automatically installed if needed when the build stage is completed!

Continuing with QT bindings generation

Creating a QApplication in lua

Building and displaying a simple widget in lua

Adding initial support for MainWindow

Adding support to set application icon

Adding support for MenuBar and first event handling

And on top of that, if we build a customization in the QObject class, then that customization will not propagate to the derived classes on module loading, so finding the function may then take some additional time… But actually this is really something I would like to confirm to be workign as expected in fact. So let's forget about the LLS confusion above for a moment, and see if we could continue the implementation directly in lua from this point…

I thus created the following QT extension module:

logDEBUG("Loading QT extensions...")

local qt_connections = {}

-- helper function used to connect lua functions to QT signals
---@param sigName string The signal name with argument list
---@param func function The lua function to be executed as a callback
---@return QMetaObject.Connection con The resulting connection object
function Qt.QObject:connect(sigName, func)
    -- Extract the list of arguments from the signal name:
    local idx = sigName:find("%(")
    local argList = sigName:sub(idx + 1, -1)
    logDEBUG("List of argument names: '" .. argList .. "'")
    local slotName = "1handle(" .. argList .. ")"
    local con, obj = self:lua_connect("2" .. sigName, slotName, func)

    -- Store the connection/luaconnector in a tracking table:
    qt_connections[con] = obj

    -- return the connection:
    return con --[[@as QMetaObject.Connection]]
end

And then we try to use that new “connect” function from our QAction object:

    local menu = mbar:addMenu("&File") --[[@as Qt.QWidget]]
    local act = Qt.QAction(gutils.createIcon("folder-open-document"), "Open file...", self.win)
    act:connect("triggered(bool)", function() logDEBUG("Hello world!") end)

    -- act.triggered.connect(lambda: self.ctx.handle("open_fci_l1c_files", self))
    -- menu:addAction(act)
    -- act:connect()
    Qt.QWidget.addAction(menu, act)

And… that doesn't work at all 😭 as the function is considered to be missing:

2023-03-07 16:55:44.886653 [DEBUG] Done loading QT bindings...
2023-03-07 16:55:44.886656 [DEBUG] Initializing QT app
2023-03-07 16:55:44.900757 [FATAL] Error in lua app:
app.QTApp:40: attempt to call method 'connect' (a nil value)
stack traceback:

That's not so good…🥴 I'm pretty this is because this new function is not registered on the derived classes then… let's see… oohhh, also, one thing to take into consideration here is that I was not using the metatable to define the function, that may also explain the error! ⇒ checking this further. Nope, doesn't work

Okay! So a simple solution that will actually work here is instead to call again __luna.copyAllParents(Qt) at the end of the QT extension file to ensure the method is copied as needed! 👍 Not the most elegant path, but I can live with that for now. And surprisingly, I then get the slot function to be executed when I trigger the signal, cool 😎.

Now time to execute the actual lua function provided to the constructor instead, that should be simple enough:

QLuaConnector::QLuaConnector(luna::LuaFunction& func)
    : _func(func.state, func.index, true) {}

void QLuaConnector::handle(bool val) { _func(val); }

And indeed, with that change, we get out lua callback executed just as expected 🥳! All right!

Conclusion

Oki oki, so we make some interesting progress here: we started with the QT6 bindings generation support on linux, and when all the way to an actually working minimal example constructed in lua and we even get the signals/slots mechanism to work without the need to rely on the QT “Meta-Object Compiler” (MOC) [I guess you start to know me: I'm the one dictating to other piece of software how I want to build my project, and not the other way around 🤣].

So this is pretty nice and all, but I already have an idea on how to improve on this: you see, in the act:connect() call above I would really want to discard the need to specify the signal function signature (ie. “triggered(bool)” in this case), and I think I can do that with some limited changes to the NervBind layer ;-) We'll handle that in our next session!