I recently released a big update for my Mac ROM SIMM Programmer software which is written using Qt for cross-platform compatibility. As part of the update I wanted to release the Mac build as a universal x86_64/arm64 binary so that M1/M2 Mac users would be able to run it natively. It doesn’t currently compile for Qt 6, although I think I can fix that in the future without too much effort. However, Qt 5.15.9 and later do support creating universal binaries out of the box, so I decided to figure out how to set it all up.

Even though I think I have pretty decent Google-fu, it was difficult to piece everything together to accomplish this goal. I’m hoping this post can serve as a reference for people in the future. These instructions are based on Qt 5.15.10 because that is the latest version of 5.x that is currently open source. I did this on an M2 Mac Mini running macOS 13.5.1 Ventura.

First of all, before anybody suggests it, you can’t use homebrew to install Qt for this because it doesn’t supply a universal build of Qt. If you’re going for an open-source build, you will need to build your own Qt instead. I’m assuming people with a commercial license can just get compiled binaries from Qt. So if you have access to that, you can probably skip the step of building Qt. Otherwise, follow along with me…

Read the rest of this entry

Have you ever wanted to be able to call a C++ Qt library from Python? It turns out it’s pretty easy to do. This tutorial explains how to do it in Mac OS X, but it’s slightly out of date. I’d like to provide the world with a way to make it work with Ubuntu 14.04 out of the box. My instructions are based on that tutorial and the PySide Binding Generation Tutorial. Let’s check it out. Note: This mechanism seems to work for wrapping Qt 4 libraries. I don’t think PySide works with Qt 5, at least in Ubuntu 14.04. Keep that in mind. Everything we do will be with Qt 4.

We’re going to create a library called libdougtest. It’s going to contain a QObject subclass called TestClass. We will create a Python wrapper for it called libDougTestPy, which we will eventually be able to refer to as DougTestPy to follow Python’s library naming conventions. Let’s get started.

Install some prerequisites

There are many things you need installed:

sudo apt-get install build-essential qt-sdk python-pyside* libshiboken-dev shiboken pyside-tools libpyside-dev

I got a warning saying I should install a Phonon backend when I did this, so I also installed the package phonon-backend-gstreamer just in case.

Create a project directory

We’re going to start out by creating a directory for this entire project. Let’s call it  dougtest_project:

mkdir dougtest_project
cd dougtest_project

All work we do will be inside of this directory. The reason I’m naming it dougtest_project instead of just dougtest is because I’m going to create another directory inside of it called dougtest for the C++ library itself.

Create libdougtest

Now, let’s create the good old C++ library that we want to wrap. This is pretty easy, since you just follow the standard Qt way of creating a library. For simplicity, I’ve provided a barebones library containing a single class. We are going to create a directory inside of dougtest_project called dougtest which will be the home for this library:

mkdir dougtest

First of all, create the project file:

dougtest/dougtest.pro:

QT += core
TARGET = dougtest
TEMPLATE = lib

HEADERS += testclass.h
SOURCES += testclass.cpp

Now, create the header and source files:

dougtest/testclass.h:

#ifndef TESTCLASS_H
#define TESTCLASS_H

#include <QObject>

class TestClass : public QObject
{
    Q_OBJECT
public:
    TestClass(QObject *parent = 0);
    virtual ~TestClass();
 
    void doSomething();

private:
    int value;
};

#endif

dougtest/testclass.cpp:

#include "testclass.h"

TestClass::TestClass(QObject *parent) :
    QObject(parent),
    value(0)
{
 
}

TestClass::~TestClass()
{
}

void TestClass::doSomething()
{
    value++;
    qDebug("Testing: %d", value);
}

This concludes the creation of the library. Let’s make sure it compiles:

cd dougtest
qmake-qt4
make

If you’re successful, you should see libdougtest.so.1.0.0 with several symlinks. Change back to the main directory:

cd ..

OK, we have successfully created the library that we’re going to wrap. We haven’t seen anything special yet; the fun is coming now.

Create files for shiboken

Next, we’re going to create some files that shiboken will use during the wrapping process. shiboken is a binding generator for creating Python bindings for C++ libraries, which is exactly what we want.

mkdir data

Create the following two files:

data/global.h:

#undef QT_NO_STL
#undef QT_NO_STL_WCHAR

#ifndef NULL
#define NULL 0
#endif

#include <QtCore/QtCore>

#include <pyside_global.h>
#include <testclass.h>

data/typesystem.xml:

<?xml version="1.0"?>
<typesystem package="DougTestPy">
    <load-typesystem name="typesystem_core.xml" generate="no"/>
    <object-type name="TestClass"/>
</typesystem>

These files specify the classes to wrap, and will be passed as parameters to shiboken, as you will see shortly.

Create wrapped library project file

Next, we need to create a Qt project that will generate the wrapper library. Create the directory DougTestPy (inside of the main dougtest_project directory):

mkdir DougTestPy

Create the file DougTestPy/DougTestPy.pro:

TEMPLATE = lib
QT += core

# Need to have access to header files from the library being wrapped.
INCLUDEPATH += ../dougtest

# PySide include paths
QMAKE_CXXFLAGS += $$system(pkg-config --cflags pyside)
INCLUDEPATH += $$system(pkg-config --variable=includedir pyside)/QtCore

# PySide dependencies
LIBS += $$system(pkg-config --libs pyside)

# Link against the library being wrapped
LIBS += -L../bin/ -ldougtest

# Generate the wrapper library in the bin directory
TARGET = ../bin/DougTestPy

# Here are all the sources that go with it
SOURCES += \
    DougTestPy/dougtestpy_module_wrapper.cpp \
    DougTestPy/testclass_wrapper.cpp

This script may be confusing because it refers to source files that we haven’t created yet. This is the naming scheme that shiboken will use when we use it to create the wrapper code.

Create the build script and run it

We are finally ready to create a shell script that will call shiboken and do everything that needs to be done. I hope this isn’t too complicated. It’s very possible that a Makefile would work too, but this is what I have working.

Create the file build.sh inside the main dougtest_project directory:

#!/bin/sh

# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, e.g. /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")

WRAPPER_NAME="DougTestPy"
WRAPPED_NAME="dougtest"

QT_INC=$(pkg-config --variable=includedir QtCore)/..
QTCORE_INC=$(pkg-config --variable=includedir QtCore)
PYSIDE_INC=$(pkg-config --variable=includedir pyside)
QTTYPESYSTEM=$(pkg-config --variable=typesystemdir pyside)

# I'm assuming the wrapped library is built in-place; if not,
# change WRAPPED_BIN_DIR to the location of the final binaries.
WRAPPED_SRC_DIR="${SCRIPTPATH}/${WRAPPED_NAME}"
WRAPPED_BIN_DIR="${SCRIPTPATH}/${WRAPPED_NAME}"

cd $SCRIPTPATH

# Clean up the old build to ensure we start fresh. If we don't do this,
# old stale stuff can be left behind, which can cause crashes in the
# library after you make changes.
rm -rf ./bin ./${WRAPPER_NAME}/Makefile ./${WRAPPER_NAME}/${WRAPPER_NAME}

# Copy the latest C++ library build into the bin directory.
mkdir -p bin
cp -R ${WRAPPED_BIN_DIR}/lib${WRAPPED_NAME}* ./bin/

# Now work inside the wrapper directory
cd $WRAPPER_NAME

# Create the wrappers for the library
shiboken ../data/global.h \
    --include-paths=$WRAPPED_SRC_DIR:$QT_INC:$QTCORE_INC:$PYSIDE_INC \
    --typesystem-paths=../data:$QTTYPESYSTEM \
    --output-directory=. \
    --avoid-protected-hack \
    ../data/typesystem.xml
if [ $? -ne 0 ]; then
    echo "shiboken returned error. Something is shibroken :-("
    exit 1
fi

# Build the python wrapper library
qmake-qt4
if [ $? -ne 0 ]; then
    echo "qmake-qt4 returned error."
    exit 1
fi

make
if [ $? -ne 0 ]; then
    echo "make returned error."
    exit 1
fi

# Back into the directory containing the script
cd ..

# Make meaningful symlink so Python can use the generated library
rm -rf ./bin/${WRAPPER_NAME}.so
ln -s lib${WRAPPER_NAME}.so ./bin/${WRAPPER_NAME}.so

OK, you’re ready to run this build script:

sh build.sh

Assuming everything succeeds, you should end up with a bin directory with the following contents:

  • DougTestPy.so (a symlink to libDougTestPy.so)
  • libDougTestPy.so, libDougTestPy.so.1, libDougTestPy.so.1.0, and libDougTestPy.so.1.0.0
  • libdougtest.so, libdougtest.so.1, libdougtest.so.1.0, and libdougtest.so.1.0.0

The symlink has to be created because the build process generates a library that begins with “lib”, but Python expects the library to not be prefixed with “lib”. Maybe there is a way to fix it so that qmake doesn’t add the “lib” prefix, but I couldn’t figure out how after a quick search, and this did the trick, so I let it go.

Test the library

We’re ready to test it out. Create the file test.py in the main dougtest_project directory:

import sys
sys.path.append("bin")
from PySide.QtCore import *
from DougTestPy import *

# Create a TestClass instance
tc = TestClass()

print "Created test class"

# Test the TestClass
tc.doSomething()

print "Called test class function"

tc.doSomething()

print "Called test class function again"

Now, run it. Note that I had to add the “bin” directory to Python’s path in order to use the library. We’re not done with library trickery yet though: libDougTestPy.so depends on libdougtest.so. So libdougtest.so needs to be in the system library path. The easiest way to do this temporarily is to add the bin directory to LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=bin/

For a more permanent solution, you could put libdougtest.so* into /usr/local/lib and run “sudo ldconfig” to eliminate the need for that hack.

Now, run the test script:

python test.py

You should get this output:

Created test class
Testing: 1
Called test class function
Testing: 2
Called test class function again

Conclusion

I hope this was helpful. It’s really not that bad; it’s just that it requires a lot of manual setup. It would be nice if there was something that could automatically generate all the junk we had to make by hand. Maybe such a tool already exists and I’m wasting my time. Either way, I think this is interesting. Thanks again to the people who wrote the tutorials I linked above.

Note that if you use libraries other than QtCore, you will need to add them into the appropriate places in data/global.h, data/typesystem.xml, DougTestPy/DougTestPy.pro, and build.sh. Just see where the word “core” or “Core” appears, and add your extra libraries such as QtGui and QtXml in the same format.

A project I’ve been working on uses an SVN repository for its version control. I needed a way to automatically insert the SVN revision into the code at compile time so that an “about” window could be automatically updated to display the current revision number. I’d like to share how I did it, both for my own future reference and for anyone else out there.

I got this working on a Linux build host, but I’m not sure exactly how well it will work on Windows. If you have Windows versions of sh, svnversion, and sed, it might be possible. I would assume it should work on Mac OS X just fine too. Let me know how it goes for you below.

Start out with a shell script

Let’s make a shell script that will grab the revision using the svnversion command. This script will go somewhere in the project directory. In my example, it’s going to go in a directory called “scripts” inside the main project directory. The script will be called updateSVNVersion.sh. You should make it executable with “chmod +x”.

#!/bin/sh
SVNVERSIONSTR=$(svnversion "$1" | sed -e "s/.*://")
VERSIONFILE="$2/version.cpp"

echo "/* This file is auto-generated by the build script. Do not modify it. */" > "$VERSIONFILE"
echo "#include \"version.h\"" >> "$VERSIONFILE"
echo "QString SVNVersionString() {" >> "$VERSIONFILE"
echo "    return \"${SVNVERSIONSTR}\";" >> "$VERSIONFILE"
echo "}" >> "$VERSIONFILE"

This script takes two parameters. The first parameter is the main project directory to run the svnversion command on. The second parameter is the build directory, which is where all the object files are stored during compilation. This is also where we will generate a file called version.cpp. The build directory is passed as a parameter so that the script knows where to put the generated file.

The output of svnversion is passed to sed to grab the second half of the version string if there are two halves separated by a colon. This ensures we grab the newer revision number of a mixed-revision checkout (this happens if you commit a file and then don’t update the full checkout, for example). This generated version string may also contain other letters such as M if there are pending changes that haven’t yet been committed.

Create version.h in the project directory:

#ifndef VERSION_H
#define VERSION_H

#include <QString>

QString SVNVersionString();

#endif // VERSION_H

All this file does is provide a prototype for the function in the auto-generated version.cpp file. Any code that needs access to the SVN revision will #include “version.h” and call SVNVersionString() to get it.

Now that we have the script, we need to make it automatically run before each build.

Don’t use a Qt Creator pre-build step…

I know someone will suggest just adding a pre-build step in Qt Creator to run a shell script. That’s great, except Qt Creator’s pre-build step is a user setting that isn’t stored with the actual project file. Pre- and post-build steps are stored in a .pro.user file that is used by Qt Creator. qmake knows nothing about it. The project I am working on has multiple developers, and it would be a big pain to make sure that everyone had their .user file set up correctly. Instead…

…do it in qmake/make

Doing it this way will work better.

The thing that’s annoying about setting it up this way is you have to make sure you get the dependencies correct so that version.cpp is recompiled every time you re-run make. I ran into several problems trying to put the SVN revision directly into header files until I finally pieced together this solution which made it easier to force version.cpp to be recompiled every time. No fear: just follow these steps and everything should work fine.

For the rest of these instructions we will be operating on your project’s .pro file. Add the generated version.cpp file to your SOURCES variable:

$$OUT_PWD/version.cpp

Note that I prefixed version.cpp with $$OUT_PWD. OUT_PWD is a qmake variable that points to the build directory. As we’ll see in a bit, there is also a variable PWD that points to the directory containing the current file, which in this case is the .pro file. Anyway, this entry may stick out like a sore thumb next to the rest of the entries in your SOURCES variable, but you have to do it this way because version.cpp is not stored in the same directory as the rest of your source files.

Next, add version.h to your HEADERS variable:

version.h

That one’s not complicated!

Finally, add some rules (again, somewhere in your .pro file) that will cause extra items to be added to the final Makefile to force version.cpp to be auto-generated:

PRE_TARGETDEPS += version.cpp
QMAKE_EXTRA_TARGETS += svnrevision
svnrevision.target = version.cpp
svnrevision.commands = $$PWD/scripts/updateSVNVersion.sh $$PWD $$OUT_PWD
svnrevision.depends = FORCE
QMAKE_DISTCLEAN += $$svnrevision.target

I’m honestly not a huge qmake or make expert, but I’ll explain these to the best of my ability. PRE_TARGETDEPS ensures that version.cpp is added early in the dependencies list, although I’m not 100% sure whether it’s necessary. I left it in place because it seems to work. QMAKE_EXTRA_TARGETS adds another target internally named svnrevision, which we set up in the next three lines. These lines create a Makefile target called version.cpp which is generated with the updateSVNVersion.sh script. Notice how we pass the source and build directories to the script as expected. The dependencies for this target are set to FORCE to force the target to run its command every time. This is a common thing done in Makefiles, and Makefiles generated by qmake do indeed include the FORCE target so it appears to be safe to use.

Basically, it causes this extra target to appear in the generated Makefile:

version.cpp: FORCE
        /path/to/updateSVNVersion.sh /path/to/source_dir /path/to/build_dir

Don’t add these lines to your Makefile; that will happen automatically. This is just an example to show what the end result looks like.

Finally, version.cpp is added to QMAKE_DISTCLEAN so it’s deleted if you run make distclean. Like I said earlier, I’m not an expert at getting qmake or make to work correctly, so I might have added some unnecessary extras. The important part is that this combination works for me!

One last thing: you may get a warning that version.cpp is not present when you run qmake the first time. It’s a harmless warning that occurs because the file doesn’t exist until you run make. Just ignore it and everything will still work fine.

I recently bought a chumby one from Woot. It’s an extremely hacker-friendly device with a 454 MHz Freescale i.MX233 ARM processor, 3.5 inch touchscreen, USB port, accelerometer, speaker, internal USB wi-fi module, and an internal microSD card. It boots from the microSD card, so it’s pretty much un-brickable as long as you keep a backup of the original SD card contents.

It’s an awesomely cool device out of the box, but all of the GUI stuff is based on Flash. Now that’s wonderful and all, but I’m just not much of a Flash guy. I really like working with Qt on embedded devices, so I got a cross compiler up and running, allowing me to design stuff on my desktop computer (running Ubuntu 10.04) and deploy it onto the chumby. I have learned during my time as a developer to document what I did when I do things like this! Two years later it’s hard to remember exactly what I did. I learned this the hard way and now I always document a procedure like this as I’m going through it. I figured while I’m documenting it, I might as well share it with the world. These instructions will walk you through creating a modern cross compiler toolchain for the i.MX233 (compatible with the chumby’s libc), using that toolchain to compile Qt/Embedded 4.7.2, and finally, creating apps on your build machine and running them on the chumby.

The basics

Start out with an Ubuntu 10.04 (“Lucid Lynx”) installation. This procedure will probably work in newer and older versions, but I’m assuming you’re using 10.04. I’m going ahead and using an amd64 install of Ubuntu, but it should work fine in an i386 install as well. Once you have that installed (however you want to do it — directly on the computer, Wubi, in a virtual machine, or whatever other crazy install method you can conjure up), we’re ready to begin.

First of all, we need to install some prerequisite packages for various purposes. In a terminal window, type:

sudo apt-get install build-essential bison flex texinfo automake libtool cvs libncurses5-dev subversion

crosstool-NG

After getting all this stuff downloaded and installed, we’re ready to start creating the cross compiler. Download crosstool-NG (I used version 1.10.0) and unpack it somewhere. crosstool-NG is absolutely amazing. It saves so much trouble while creating cross compilers. You just go through a menuconfig interface similar to the Linux kernel’s menuconfig interface, telling it exactly what you want. If you’re lucky, you just tell it to work and it grabs all the source code, patches it, and compiles it automatically, leaving you with a fully-functional cross compiler after a half-hour or so. In this case, I have verified that the configuration I’m going to give you will work. In other cases, you may have to do some trial and error because some versions of C libraries and binutils don’t work with some versions of gcc. Generally you want to use tools that came from the same time period, as this will give you a better chance of everything working together. Anyway, go into the crosstool-ng directory in a terminal, and type:

./configure --local
make

The –local option tells crosstool that we will be running it directly out of the directory we unpacked it to. Otherwise it would install itself into /usr/local or somewhere like that. I prefer it this way rather than putting it into /usr/local. This should create a script called ct-ng. We will be using this script to configure and create the toolchain.

So let’s start setting up the toolchain! I’d just give you the config file, but I’d rather walk you through setting up crosstool so you better understand how it works. Type:

./ct-ng menuconfig

After a few things finish setting up, you’ll be greeted with graphical interface in the terminal. You can move up and down with the arrow keys. The enter key will go to the selected item. Hit the Esc key twice to go back. Let’s go through the sections:

Paths and misc options

It turns out I’m going to break my own rules here. I’m going to choose newer compilers and binutils with an older glibc. It happens to work in this case, so no harm done. I’m using an older glibc because the glibc on the chumby’s root file system is version 2.8, so I’d like our version to match. If we use a newer version, some binaries will not be compatible with the older glibc, so we’d have to replace chumby’s glibc, and I’d rather not.

Highlight Use obsolete features and press the space bar to select it. This will allow us to choose an older glibc that is probably not compatible. (We’re actually going to use eglibc, which plays better with our setup than glibc)

Scroll down and you’ll notice that Prefix directory is set to “${HOME}/x-tools/${CT_TARGET}. This means it’ll create a folder called x-tools in your home directory which will store all cross toolchains you create. I left this alone, but if you’d like it elsewhere, go ahead and change it now.

Keep scrolling down until you find Number of parallel jobs. This one will save you some time if you have a dual- or quad-core processor. While creating the toolchain, this will allow some of the files to be compiled concurrently, making better use of your multiple-core CPU. For you techies out there, it’s letting you specify the -j option passed to the make commands that will run. I generally set this to the number of cores I have available. Since my 4-core Core i7 has hyperthreading, I normally choose 8. In a VMware virtual machine where I have given the VM two cores to work with, I’d choose 2. Different people have different opinions on what’s the best value here, but I’d say stick with the number of cores you have available and you’ll be fine. If you pick too high of a value your entire system will slow to a halt, and if you pick too low of a value, you’ll be under-utilizing your CPU.

Okay, sorry about that long explanation. Now we’re ready to move to the next section. Hit Esc twice to go back to the menu. Move down to Target options and hit enter.

Target options

Highlight Target Architecture (alpha) and hit enter. Scroll down to arm and hit the space bar. We are telling crosstool-NG that we’re building a cross compiler that will target the ARM architecture, which is what the chumby uses.

chumby uses a Freescale i.MX233, which is an ARM926EJ-S. We need to tell crosstool which CPU we’re targeting, so scroll down to Emit assembly for CPU and hit enter. Type “arm926ej-s” (without the quotes) and hit enter.

Scroll down to Floating point and hit enter. Scroll down to software and hit the space bar. The i.MX233 does not have a hardware floating point unit, so we need to choose softfloat here (otherwise, the kernel emulates hardware floating point instructions, and it gets terrible performance).

We’re done here, so hit Esc twice, move down to Toolchain options, and hit enter.

Toolchain options

Our toolchain will be called “arm-chumby-linux-gnueabi”. The “chumby” part of this is called the vendor string, so we need to configure it as such. The vendor string makes no technical difference–it’s purely a cosmetic thing. Scroll down to Tuple’s vendor string, hit enter, backspace until you have erased “unknown”, type “chumby”, and press enter.

Exit this menu and go to the Operating System menu.

Operating System

Change Target OS to linux. You’ll notice that the kernel version is set at 2.6.37. It may not make a huge difference in some cases, but in this case, we need to pick a kernel version closer to the chumby’s kernel (2.6.28-chumby). Otherwise, the touchscreen library won’t work. We could probably get a tarball of the chumby’s kernel and be exact, but rather than bother with that, I set it at 2.6.27.57. Once you’ve made that change, exit this screen and go to Binary utilities.

Binary utilities

We’re not going to change anything here, but this is where you would change the version of binutils. Leave it at version 2.20.1.

Exit again and go to C compiler.

C compiler

Leave gcc at version 4.4.5. You need to scroll down under Additional supported languages to C++ and hit the space bar to make sure the toolchain is enabled for compiling C++. Now move down to C-library.

C-library

The C library is already set as eglibc, which is what we want. Change eglibc version to 2_8 (OBSOLETE). Normally this would be a bad idea to use an old version of eglibc combined with newer binutils and gcc, but we need version 2.8 in order to be compatible with the chumby’s provided glibc, and it works OK with the newer binutils and gcc. This is the sole reason we had to enable Use obsolete features when we were starting out. We also need to add an extra option here to fix a problem I encountered when I first tried to build this toolchain. Go to extra target CFLAGS and set it to “-U_FORTIFY_SOURCE”. Exit this screen and head over to Debug facilities.

Debug facilities

Go ahead and enable gdb (leaving it at version 6.8). We probably won’t use it, but it won’t hurt to have it. Leave this screen.

All done configuring

We don’t need to mess with anything else, so hit Esc twice and you will be asked if you want to save your new configuration. Hit enter, and you’re ready to build your toolchain!

Type:

./ct-ng build

crosstool-NG will update you on the status of your build as it keeps going on, but for now you can sit back and relax. If you’d like, skip ahead and start downloading some of the other stuff you will need to have as you progress further through these instructions. Depending on the speed of your computer, this compilation might take 15 minutes to an hour.

If all goes well, crosstool-NG will finish by saying “Finishing installation (may take a few seconds)…” followed by a shell prompt. At this point, your cross toolchain is complete! Assuming you left it to be installed at the default location, you can now compile programs for the chumby by using:
~/x-tools/arm-chumby-linux-gnueabi/bin/arm-chumby-linux-gnueabi-gcc

I made a quick test app:

chumbytest.c:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("It works!\n");
}

compiled it:

~/x-tools/arm-chumby-linux-gnueabi/bin/arm-chumby-linux-gnueabi-gcc chumbytest.c -o chumbytest

and transferred it to the chumby with scp to make sure it worked before advancing any further (assuming ssh has been enabled on the chumby):

scp chumbytest root@MyChumbysName.local:/tmp

to run it on the chumby over ssh:

ssh root@MyChumbysName.local
/tmp/chumbytest
exit

As long as it worked as expected, you’re ready to start working on getting Qt compiled. Well, kind of.

tslib

We need to do one prerequisite in order to get the touchscreen working, and it’s a library called tslib. Download it and extract it.

Start out inside the tslib directory and type:

./autogen.sh

Let’s go ahead and add our cross compiler to our path:

export PATH=~/x-tools/arm-chumby-linux-gnueabi/bin:$PATH

Now we’re ready to configure tslib to be cross compiled, and we’re going to tell it to install itself into a temporary directory inside our current directory. There are two hacks we have to do here–the first is we define a variable ac_cv_func_malloc_0_nonnull to be yes to avoid a compile error, and we also add -U_FORTIFY_SOURCE to the CFLAGS to avoid other errors, the same way we had to during the compilation of eglibc. Here’s the complete command:

ac_cv_func_malloc_0_nonnull=yes ./configure --prefix=$PWD/tslib --host=arm-chumby-linux-gnueabi CFLAGS=-U_FORTIFY_SOURCE

Compile it:

make -j 2 (or 4 or 8 or whatever, depending on how many cores you have)

Install it:

make install

Now there should be a tslib subdirectory inside the tslib directory, containing the compiled library, plugins, and a config file in etc. We really need to put the tslib library and include files into the appropriate place in our cross compiler’s sysroot so we can use it when compiling. Type:

sudo cp -R tslib/lib/* ~/x-tools/arm-chumby-linux-gnueabi/arm-chumby-linux-gnueabi/sysroot/usr/lib

to copy all the libraries into the cross compiler. We should also copy the include files in there:

sudo cp tslib/include/tslib.h ~/x-tools/arm-chumby-linux-gnueabi/arm-chumby-linux-gnueabi/sysroot/usr/include

This will simply make it easier in the future when compiling, so we don’t have to specify where the libraries are located. Now, let’s get the touchscreen working on the chumby before we compile Qt. Transfer the compiled tslib subdirectory to the chumby’s storage:

tar -cf - tslib | ssh root@MyChumbysName.local tar -xf - -C /mnt/storage

Now we need to get a shell into the chumby, quit the chumby control panel, and set up a few environment variables so we can calibrate the touchscreen:

ssh root@MyChumbysName.local
/usr/chumby/scripts/stop_control_panel
export LD_LIBRARY_PATH=/mnt/storage/tslib/lib:$LD_LIBRARY_PATH
export TSLIB_TSDEVICE=/dev/input/by-id/soc-noserial-event-ts
export TSLIB_CALIBFILE=/mnt/storage/tslib/etc/pointercal
export TSLIB_CONFFILE=/mnt/storage/tslib/etc/ts.conf
export TSLIB_PLUGINDIR=/mnt/storage/tslib/lib/ts

Edit /mnt/storage/tslib/etc/ts.conf and uncomment the “module_raw input” line.

You’re now ready to calibrate the touchscreen! Type:

/mnt/storage/tslib/bin/ts_calibrate

If all goes well, you should see “TSLIB calibration utility” and “Touch crosshair to calibrate” appear on the screen. Touch the crosshair for each corner and then the center, and the app will exit (the screen will stay the same, though). Now you’re calibrated! Test it by typing:

/mnt/storage/tslib/bin/ts_test

You should be able to drag the crosshair around on the screen. Hit ctrl-C to exit.

OK, so your touchscreen setup is now complete. You know all of those export commands you just typed before calibrating? You may want to put them into a file somewhere so they can be sourced whenever you need to run them. Whenever you want to use the touchscreen, those environment variables need to be set that way. We are now finally ready to compile Qt.

Qt

Here we go! The exciting part has arrived. We are ready to compile Qt. First, you need to download the latest source code for Qt. Pick the LGPL version unless you have a commercial license. Note that these instructions are based on version 4.7.2. Once you’ve downloaded the tarball, extract it and enter its directory. We now need to set up a Qt spec for the chumby.

Spec

Inside the mkspecs directory is a list of other directories containing specs for many different architectures. There is also a directory called “qws”. We want to use a qws spec, because our Qt will be drawing directly to the frame buffer device without having to have a window server like X11 installed. Instead, Qt will be its own window server. In the qws directory, there is a spec called linux-arm-gnueabi-g++. This one is very similar to the toolchain we are using, so let’s go ahead and duplicate it:

cd mkspecs/qws
cp -R linux-arm-gnueabi-g++ linux-arm-chumby-gnueabi-g++

Now, you need to edit the qmake.conf file inside of our new linux-arm-chumby-gnueabi-g++ directory. You will see several references to tools prefixed with “arm-none-linux-gnueabi-“. You should replace this prefix with the prefix for your toolchain. In fact, I would put the full path to each of these tools. For example, change arm-none-linux-gnueabi-gcc to /home/yourname/x-tools/arm-chumby-linux-gnueabi-gcc, and so on. It may seem stupid because you could always add the directory to your path, but when we’re executing the cross compiler from inside of Qt Creator, it will be easier if the full path is already there. Once you’re done with this, save the file and we’re ready to go!

Compiling

Here is the complete command, along with descriptions of what every option does below it:

./configure -opensource -embedded arm -xplatform qws/linux-arm-chumby-gnueabi-g++ -no-feature-cursor -qt-mouse-tslib -nomake examples -nomake demos

-opensource: specify that we’re using the LGPL. If you have a commercial license you should change this.
-embedded arm: specify that we’re compiling Qt/Embedded for ARM.
-xplatform qws/linux-arm-chumby-gnueabi-g++: specify the make spec that will be used for compiling. This tells Qt where our cross compiler is, and info about the cross compiled system.
-no-feature-cursor: hides the cursor, since it doesn’t make much sense to have one with a touchscreen. If you want a cursor, you can remove this option.
-qt-mouse-tslib: tells Qt to use tslib for its mouse driver
-nomake examples: Tells Qt to skip the examples. I already know how to use Qt so I don’t need a bunch of examples compiled. If you want them, keep it there.
-nomake demos: Same as above.

There are other options you can use to turn off specific features. If you don’t envision ever using WebKit, you can turn it off (-no-webkit). Anyway, type the command above, and you’ll have to agree to the open source license. Once that’s done, it’ll take a few minutes to configure the build. When the configure is complete, type:

make -j 2 (or whatever number of cores you have available)

Once that’s done (and it’ll be a while, trust me on this one!) you can install the libraries by typing:

sudo make install

The reason you need sudo is that Qt defaults to installing in /usr/local/Trolltech. You can change this with the -prefix option on the configure script, but I don’t mind Qt residing there. Once that’s done, you should have a functional Qt. Send the Qt libraries over to the chumby:

cd /usr/local/Trolltech/QtEmbedded-4.7.2-arm/
tar -cf - lib | ssh root@MyChumbysName.local tar -xf - -C /mnt/storage

And now Qt should be on the chumby’s SD card, with its libraries stored in /mnt/storage/lib. We can’t test it until we make a quick Qt app, though, so let’s install the Qt SDK.

Qt SDK

The Qt SDK is a massive download, currently in beta, but it works just fine. We’re going to install it and use it to create a test app to make sure Qt works. First, download the SDK from the Qt download site. I downloaded the Qt SDK 1.1 Beta online installer for Linux 64-bit from here (since my development machine is 64-bit). Make it executable and run it:

chmod +x Qt_SDK_Lin64_online_v1_1_beta_en.run
./Qt_SDK_Lin64_online_v1_1_beta_en.run

At this point, you’ll need to wait for it to finish “Retrieving information from remote installation sources…”. Once that’s done, accept the defaults, allow it to install in ~/QtSDK or wherever else you want it, and eventually you’ll have to wait for it to finish downloading and installing the SDK. Once it’s done, go ahead and allow it to Launch Qt Creator, but uncheck Open Qt SDK ReadMe.

After a few moments, Qt Creator will pop up. We now need to add our cross toolchain so that Qt Creator knows about it.

Adding the cross toolchain

Click on Tools, and choose Options. On the left, click Qt4. You should see a list of available Qt versions. We are going to add a new one, so click the blue + on the right side of the window. Where it says <specify a name>, type “Chumby Qt”, and where it says <specify a qmake location>, click Browse… and navigate to your cross compiled Qt’s qmake binary (/usr/local/Trolltech/QtEmbedded-4.7.2-arm/bin/qmake). Below, it should say “Found Qt version 4.7.2, using mkspec qws/linux-arm-chumby-gnueabi-g++ (Desktop)”. It’s inaccurate in claiming that this is a desktop target, but it’s ok — it still works. Click OK to save your new Qt version.

Creating a test app

I didn’t want to turn this into a tutorial on how to use Qt, but I at least want to walk you through creating a basic app, cross compiling it, getting it onto the chumby, and running it. Click on File and choose New File or Project…. Leave Qt Gui Application highlighted and click Choose…. Name your project TestChumbyQt and save it wherever you’d like. When it asks you to set up targets, uncheck all the targets except for the Chumby Qt target we created earlier. Click Next and leave the QMainWindow class name alone (MainWindow is fine for this example). Accept all the default choices.

Now we are ready to create the test app. Expand the Forms folder on the left and open mainwindow.ui. Click on the background of the newly-created window. Next, on the right side, there should be a “geometry” property. Click the + to expand it. Change width to 200 and height to 150. This should shrink the window a bit. On the left, drag a push button onto the main window. You can resize it to make it easier to press if you’d like.

Compiling the test app

Ok, we’re ready to compile the app. Go to Build and choose Build Project “TestChumbyQt”. If it asks you to save, click Save All. You should see a progress bar on the left side and eventually it will turn green, meaning the compile succeeded.

We’re ready to go. I put my test app in ~/Documents, so your command may be slightly different from mine:

scp ~/Documents/TestChumbyQt-build-desktop/TestChumbyQt root@MyChumbysName.local:/mnt/storage

Environment variables

Now, we need to define a few environment variables in order for Qt to work. You will need to do all of the exports we did above when we were testing out tslib, so if you still have that shell open you can skip doing those again. I’m listing them again just in case, though, plus some extras you need to do regardless.

export LD_LIBRARY_PATH=/mnt/storage/tslib/lib:$LD_LIBRARY_PATH
export TSLIB_TSDEVICE=/dev/input/by-id/soc-noserial-event-ts
export TSLIB_CALIBFILE=/mnt/storage/tslib/etc/pointercal
export TSLIB_CONFFILE=/mnt/storage/tslib/etc/ts.conf
export TSLIB_PLUGINDIR=/mnt/storage/tslib/lib/ts

And the ones you need to do:

export QWS_DISPLAY="LinuxFb:mmWidth80:mmHeight52"
export QT_QWS_FONTDIR=/mnt/storage/lib/fonts
export QWS_MOUSE_PROTO=tslib

The QWS_DISPLAY variable tells Qt about the physical dimensions of the chumby’s screen in millimeters. I did a very crude measurement with a ruler. If you don’t specify it, all the text will appear tiny because we’re working on a small screen. QT_QWS_FONTDIR tells Qt where to look for its fonts. Because it thinks it’s stored in /usr/local/Trolltech, we need to specify this so it knows where it should actually look. Finally, QWS_MOUSE_PROTO tells Qt to use tslib rather than the generic Linux input system.

As I said earlier, you will want to make a file you can source that includes all of these environment variable definitions — they are necessary in order to start a Qt app. Notice I didn’t add /mnt/storage/lib (where we put the Qt libraries) to LD_LIBRARY_PATH — the reason I didn’t is because the chumby already has that directory in its pre-supplied LD_LIBRARY_PATH, so I didn’t want to put it in there twice.

Running the test app

We’re ready to go now. In an SSH session to the chumby, type:

/mnt/storage/TestChumbyQt -qws

(-qws tells TestChumbyQt to be a window server. If you run other apps while it’s running, you don’t want to pass -qws to them–only one app should be the window server.) You may see an error about being unable to open /etc/pointercal, but that’s okay–our environment variable tells it to look elsewhere anyway. If all goes well, your window should appear on the chumby’s screen. Try pressing the button and dragging the window around. Everything should work! You can quit the app by pressing control-C in the terminal, or tapping the X in the upper right corner of the window.

Congratulations, you now have Qt working on the chumby. There are ways to get a Qt app to run at bootup now rather than the flash-based GUI, but that’s beyond the scope of this document. All that’s important is now you know how to run Qt apps.

All done

Unfortunately, I just don’t have the bandwidth necessary to host any binaries or source code for this. However, I have (hopefully) provided you with all the info you’ll need to create the libraries yourself. I have not yet figured out how to make a Windows-based toolchain for this — I know how to create the ARM cross compiler for Windows, but I just don’t know how to get Qt to compile inside of Windows. If I ever discover how, I’ll make another post about it. So for now, you will need a Linux environment for your development.

I know some of what I did was a little crude — I should have specified the ultimate destination directory for tslib when I configured it with –prefix, and I also probably could have specified in the configure script some of the paths that I had to redefine with environment variables, but this works for now. There’s nothing special about /mnt/storage, by the way — I just picked it because it was easy to put stuff there. It might be better suited for a special directory in /usr or something along those lines.

If you run into any problems, feel free to leave a comment and I’ll try to figure out what’s wrong. Enjoy!

Edit 2: It looks like Qt 5.0 will now give us public access to QWinEventNotifier, so the contents of this article are definitely still useful. Thank you Leo (see the comments below) for pointing that out!

Edit: On April 30, 2011, Sebastian informed me that this approach no longer works in Qt 4.7.2. It looks like we no longer have access to the private header files. See the comments for more information where I describe what has changed and mention a few workarounds. My post continues unmodified below in case people are using older versions, or if we regain access to the QWinEventNotifier class in the future.

Lately I’ve been working with a CAN-USB adapter. I’ve been interfacing to it through a Qt GUI app. When you’re dealing with CAN, you need some kind of notification that you’ve received a message. On a microcontroller, an interrupt tells you when a message has arrived. With this CAN adapter, the driver needs to send some kind of notification to your app. Otherwise, you’re constantly polling to see if a new CAN message has arrived (and using up your computer’s CPU time needlessly).

This particular CAN adapter has both Linux and Windows drivers. The two drivers use slightly different APIs, so I’ve been using #ifdefs for the Qt app to work on both platforms.

On Linux, the driver gives you a file descriptor that you can pass to select() or a similar function to wait for activity, similar to how you’d wait for activity from a socket. In fact, that’s exactly how you handle it in Qt — you make a QSocketNotifier with the file descriptor the driver library gave you, and then the QSocketNotifier fires its activated() signal whenever a CAN message comes in. So this effectively integrates the USB to CAN adapter into the main thread’s event loop.

Dealing with this device in Windows is a little more complicated. First of all, the driver DLL doesn’t work with MinGW out of the box, but there are tutorials on the web with instructions to get a DLL compiled with Microsoft’s compiler to work correctly with MinGW. I can’t remember exactly how I did it, but those tutorials helped me with that. It was something about the way the functions in the DLL export file were named. Maybe I’ll write another blog post about that later, if I can figure out how I did it the first time. Bad Doug!

Anyway, the other thing about the Windows driver is that it doesn’t give a file descriptor to notify you that a CAN message has arrived. Instead, you’re supposed to pass it a Windows event handle, which it will then put into the signaled state whenever a message comes in. So you’re supposed to create a Windows event object with CreateEvent() and give the newly created event handle to the CAN library. Then, in a Windows app, you would use WaitForSingleObject() or something like that to wait for the driver to signal you.

Using this info, you could already solve the problem in a Qt app. Create a new thread in Qt that sits in a while loop, calling WaitForSingleObject() with an unlimited timeout. Whenever the event object is signaled, emit a Qt signal. You can then attach a slot in the main thread to that signal, and then you will be notified whenever a new CAN message is ready. This approach works, but it just seems like overkill because you’re creating a new thread with its own mini event loop just to forward the event to Qt’s event loop. Qt’s main thread already has an event loop, so why not just get the main thread’s event loop to listen for the event directly?

It turns out this is definitely possible to do with Qt. It’s just that the class is kind of hidden in the bowels of Qt, probably because it’s a Windows-specific thing. The class is called QWinEventNotifier, and you can use it in a Qt app by doing:

#include <QtCore/private/qwineventnotifier_p.h>
(Thanks, QExtSerialPort, for showing how to #include this class in your code–I saw it in Qt’s source code, but I couldn’t figure out how to add it in my program.)

From this point on, it’s simple. 3weyuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu (Sorry, that was the kitten.) Just create a QWinEventNotifier, passing the event handle returned from CreateEvent() to the constructor. Connect the QWinEventNotifier’s activated(HANDLE) signal to a slot. That’s it! Qt will take care of it from there, giving you a notification whenever a CAN message is ready for reading.

You don’t have to do anything special to be able to call CreateEvent, at least in my experience. Just #include <windows.h> and then you have complete access to CreateEvent and whatever other Windows function you need. It all just works.

I’m sure this can be adapted to work with other Windows event type stuff. Since my description of this solution was a little abstract, I’ll provide a concrete (silly) code example. I create a thread that does nothing other than signal a Windows event object every second or so. This is basically simulating the CAN library signaling when a message is received. Other than that, the main thread just creates a QWinEventNotifier that receives that signal. Here ya go:

wineventtestthread.h:

#ifndef WINEVENTTESTTHREAD_H
#define WINEVENTTESTTHREAD_H

#include <windows.h>
#include <QThread>

class WinEventTestThread : public QThread
{
    Q_OBJECT
public:
    explicit WinEventTestThread(void *eventH, QObject *parent = 0);
    void run();
private:
    HANDLE eventHandle;
    bool keepRunning;
signals:

public slots:

};

#endif // WINEVENTTESTTHREAD_H

wineventtestthread.cpp:

#include "wineventtestthread.h"
#include <QDebug>

WinEventTestThread::WinEventTestThread(HANDLE eventH, QObject *parent) :
    QThread(parent)
{
    this->eventHandle = eventH;
    this->keepRunning = false;
}

void WinEventTestThread::run()
{
    qDebug() << "Test thread ID: " << QThread::currentThreadId();
    this->keepRunning = true;

    while (this->keepRunning)
    {
        // wait a second
        sleep(1);

        // signal the windows event
        if (SetEvent(this->eventHandle) == 0)
        {
            qDebug() << "Test thread: Error signaling event";
        }
        else
        {
            qDebug() << "Signaled event in thread " << QThread::currentThreadId();
        }
    }
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <windows.h>
#include <QMainWindow>
#include <QtCore/private/qwineventnotifier_p.h>
#include "wineventtestthread.h"

namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
private:
    Ui::MainWindow *ui;
    WinEventTestThread *thread;
    HANDLE eventHandle;
    QWinEventNotifier *notifier;
private slots:
    void eventSignaled(HANDLE h);
};

#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // create the windows event
    qDebug() << "Main thread ID: " << QThread::currentThreadId();

    this->eventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (this->eventHandle == NULL)
    {
        qDebug() << "Error creating event handle.";
    }
    else
    {
        // set up notifications when it's signaled
        notifier = new QWinEventNotifier(this->eventHandle);
        connect(notifier, SIGNAL(activated(HANDLE)), SLOT(eventSignaled(HANDLE)));
        thread = new WinEventTestThread(this->eventHandle);

        // start the test thread (it will start signaling the windows event)
        thread->start();
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::eventSignaled(HANDLE h)
{
    qDebug() << "Signal received in thread " << QThread::currentThreadId();
}

Voilà!