Introducing seaqt, a new set of bindings for Qt - hopefully of interest for anyone doing cross-platform GUI development.
The project is at this stage a proof-of-concept to explore where we could go next with user interfaces like status desktop - while nimqml has served us well, it lacks in coverage / access to the majority of Qt which has become a limiting factor - this is where seaqt comes in.
seaqt takes a slightly more modern approach to wrapping than nimqml/DOtherSide, namely using a clang-based parser inherited from miqt - a similar binding project for Go.
The parser spits out C wrappers which in turn are exposed to Nim via the usual importc dance. Unlike nimqt, this approach allows us to continue using nim c and a bonus on top is that we share most of the C codebase with the Go bindings. Of course,seaqt could also be used stand-alone in C applications, or as the base for other FFI-capable languages.
On top of that, there's a nimqml port to `seaqt` that removes the DOtherSide dependency and instead uses seaqt as backend - if you have an existing nimqml application, this can be used to seamlessly switch to `seaqt` and start using its facilities gradually instead of a full rewrite. Advantages include build simplification, compile-time meta-object support, low wrapper overhead and tighter integration while at the same time gaining access to all those sweet Qt modules.
Here's a sample Qt application that implements a list view, using the emulated inheritance of QAbstractItemModel:
import
seaqt/[qapplication, qabstractlistmodel, qlistview, qvariant, QtCore/gen_qnamespace]
type Model = ref object of VirtualQAbstractListModel
method rowCount(self: Model, idx: QModelIndex): cint =
1000
method data(self: Model, idx: QModelIndex, role: cint): QVariant =
if not idx.isValid():
return QVariant.create()
if role == ItemDataRoleEnum.DisplayRole:
QVariant.create("this is row " & $idx.row())
else:
QVariant.create()
proc main() =
let
_ = QApplication.create()
model = Model()
QAbstractListModel.create(model)
let v = QListView.create()
v.setModel(model[])
v.show()
discard QApplication.exec()
when isMainModule:
main()
All you need to compile it is seaqt itself, a Qt installation with -dev packages (pkg-config is used to find Qt) and Nim 2.0+ (memory management is done "mostly" via destructors).
There are of course still lots of things to work on - right now, the only DSL available for creating slots and signals is that of nimqml - there's still work to do on memory management and ownership of widgets - naming, ergonomics, module layout and really everything else is still up for grabs, which is what this post is for: an invitation to try it out, leave some feedback and/or join the seaqt org to get the project to really shine.
Further, the way the generator is written with clang doing the heavy C++ lifting, it's also pretty close to being able to wrap custom Qt libraries and widgets, or maybe even as a "general" C++ code importer (yay, yet another one!) as long as it's not too fancy. Enjoy!
Thank you very much. This is very much appreciated.
I have a meta question though which is more related to the status desktop app itself. The last time I tried it it did not feel particularly snappy at all. As in it did not feel better than an Electron application.
Was that the same experience you guys noticed or is it just me? I remember it still used QTWebEngine and was heavy.
I felt the same about Dropbox app as well (I think it used QTWebengine as well).
Are you happy with QT's performance overall?
performance
Status desktop has a pretty heavy backend running that has to deal with a lot of crypto complexity - the architecture itself also needs an overhaul, as it's currently shared with a mobile app that has completely different demands. A small part of the slowness is also due to nimqml that adds its own overhead and limitations.
I wouldn't pin desktop's performance issues on Qt itself, which I've seen work well in other applications if you let it (ie don't overload the UI event loop thread with computational work which to a certain extent is happening in desktop right now).
Re web engine, I don't know really. Seems kind of odd to host a web app in a Qt window, it would be missing the point somewhat since their whole thing is widgets, layouts and QML.
Status desktop has a pretty heavy backend running that has to deal with a lot of crypto complexity - the architecture itself also needs an overhaul, as it's currently shared with a mobile app that has completely different demands.
Understood. Would splitting the backend and GUI work a bit to give some feeling of snappiness? I think that's what the AV companies do.
Would splitting the backend and GUI work a bit to give some feeling of snappiness?
Indeed - that's already done to a certain extent but not to the extend that is needed - this is the architectural problem I hint at, that requires a redesign.
If you talk abut the QVariant serialization
One problem is the inability to return QObject* directly from signals/properties but rather having to wrap it "manually" - apart from being inconvenient in the nim code, apparently QML binding caching is affected somehow - either that or something else in nimqml's QMetaObject generation apparently causes trouble / increased traffic between QML and custom QObject instances.
I have not dug into the detail here, except to note that DOtherSide indeed uses "dynamic" metaobject generation which misses out on a few details such as a qt_static_metacall implementation etc which the seaqt backend for nimqml improves on - these papercuts add up when you have lots of QML objects bound, in a way that our Qt devs that previously worked with Qt/C++ natively say shouldn't be happening.
That said, I agree the slow performance of status desktop is mainly architectural, as I wrote above - getting rid of the papercuts would be an improvement but it would just kick the can down the road.
Would splitting the backend and GUI work a bit to give some feeling of snappiness?
nora is a little example/experiment that does this and remains fairly snappy as a result - ie networking operations are done in a separate thread while the gui thread can focus on display.
It's not in any way a ground-breaking design, but it's a direction that status desktop has grown to need and that might be appropriate for applications that have sufficiently complex computational needs.
Hey @arnetheduck
Could you give a quick rundown on how to setup seaqt on Windows? Another hope I have but haven't researched it: how hard would it be to wrap a custom Qt widget with seaqt? Example would be the Scintilla editing component, which has a Qt wrapper already.
Thanks
Ok, got one step further:
$ nim c seaqt_first_test.nim
Hint: used config file 'C:\Users\Matic\AppData\Local\grabnim\current\config\nim.cfg' [Conf]
Hint: used config file 'C:\Users\Matic\AppData\Local\grabnim\current\config\config.nims' [Conf]
........................................................................................................................................................................................
CC: gen_qapplication
CC: gen_qobject
CC: gen_qcoreevent
CC: gen_qmetaobject
CC: gen_qobjectdefs
CC: gen_qpoint
CC: gen_qfont
CC: gen_qfontmetrics
CC: gen_qguiapplication
CC: gen_qcoreapplication
CC: gen_qabstracteventdispatcher
CC: gen_qabstractnativeeventfilter
CC: gen_qbindingstorage
CC: gen_qvariant
CC: libseaqt-runtime
CC: gen_qcursor
C:\Users\Matic\.nimble\pkgs2\seaqt-0.6.4.0-ccfc438e327af82ba1d2146e1d24f033f7191bb6\seaqt\libseaqt-runtime.cpp:9:10: fatal error: private/qobject_p.h: No such file or directory
9 | #include <private/qobject_p.h>
| ^~~~~~~~~~~~~~~~~~~~~
compilation terminated.
Error: execution of an external compiler program 'g++.exe -c -std=gnu++17 -funsigned-char -w -fmax-errors=3 -mno-ms-bitfields -DWIN32_LEAN_AND_MEAN -DQT_CORE_LIB -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN6
4 -D_WIN64 -DMINGW_HAS_SECURE_API=1 -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00 -IF:/msys64/mingw64/include/qt6/QtCore -IF:/msys64/mingw64/include/qt6 -IF:/msys64/mingw64/share/qt6/mkspecs/win32-g++ -IF:/msys64/mingw64/
include/qt6
-fPIC -IF:/msys64/mingw64/include/qt6/QtCore/6.9.1
-IF:/msys64/mingw64/include/qt6/QtCore/6.9.1
/QtCore -IC:\Users\Matic\AppData\Local\grabnim\current\lib -IF:\Nim\seaqt\first -o C:\Users\Matic\nimcache\seaqt_first_test_d\@mC@c@sUsers@[email protected]@[email protected]
91bb6@[email protected] C:\Users\Matic\.nimble\pkgs2\seaqt-0.6.4.0-ccfc438e327af82ba1d2146e1d24f033f7191bb6\seaqt\libseaqt-runtime.cpp' failed with exit code: 1 If I am not mistaken the question of the qtprivate files I had solved it like this, but I should find a clean Windows and repeat the installation.
Thanks for the hint, the only change that is needed:
configure.nims
should be named whatever your main module is called:
my_main_module.nims
and now it works 👍
Optimal! I'm really happy! have fun.
If you want here: find Macro to simplify the Connect (), of QT, is still in testing but should help with the calls, if you want to try. I should put example, but first I have to translate them (now I'm in Italian).
https://github.com/Martinix75/seaqt-tools The syntax is very simple:
connect(widget, onSIGNAL, arg1, arg2)
connect(widget, onSIGNAL, callProc,arg1, arg2)The core widgetset be it desktop class or QtQuick and QML are still LGPL. This means that as long as you dynamically link Qt and the user could theoretically switch Qt libraries, it's alright even for commercial use. If one has made changes to the used Qt library itself, the modified Qt library's source has to be published in full.
This won't work for iOS though. Apple doesn't allow dynamic linking for App Store. So, one would need a commercial license for targeting iPhone/iPad.
There are App Store apps that use LGPL V3 though - VLC, Cisco Jabber for example. But they are "embedded" libraries with signed source, and they have some exceptions reg App Store as it is their own code with source published under LGPL, and allows technical users to modify and build for themselves.
There are specific tools that come with an LGPL linking exception though for Qt - KDE Kirigami, Lazarus for Free Pascal. Then, you can build even commercial apps with static linking of Qt libraries.
Pushed https://github.com/arnetheduck/ngui - apart from being a functional (albeit ugly) block chain explorer for the Ethereum beaacon chain, it demonstrates how to port an existing nimqml application to seaqt.
To get there, ngui uses nimqml-seatqt which acts as a drop-in replacement with some convenience improvements, like easier build, better integration with meta objects, better performance etc, but above all, it also offers an incremental migration path towards a more friendly DSL, as seen in nora.