Multi-screen support on Raspberry Pi 4B without windowing system
-
I'm migrating my Qt 5.12.5 based software from the Raspberry Pi 3B (RPi 3B) to the 4B but have been struggling with the new display driver (DRM/KMS) that it uses.
The software currently runs on Raspbian Buster Lite kernel 4.19.66 on the RPi 3B. It does not have a windowing system so Qt provides this with the eglfs plugin. It utilizes multiple screens attached to the RPi; a TV connected via HDMI and the official Raspberry Pi 7" touchscreen connected to DSI. The software displays a UI on the touchscreen and plays audio/video on the TV.
Apparently due to a limitation of DRM/KMS in combination with eglfs, only one Qt program can use eglfs on the RPi 4B at a time (https://doc.qt.io/qt-6/embedded-linux.html#display-output). This breaks the software because it runs a separate Qt program from the UI that displays other information on the TV.
The other thing I used to be able to do is set the environment variable: "VC_DISPLAY" before executing a Qt program which allowed specifying which screen the program should use. Setting this variable to "4" indicates the touchscreen and "5" is HDMI. I believe this variable is related to the VideoCore library. But this is no longer working either.
I have cross-compiled Qt 5.15.2 for Raspberry Pi OS Lite (Legacy) kernel 5.10.103 which is Buster. I can successfully build Qt programs and deploy to the RPi 4B. Using the QGuiApplication::screens() method, I see that the program identifies two screens (HDMI and DSI) with the expected dimensions. The problem is if I try to create two widgets and display one on each screen, only the TV is showing a widget and it appears that both widgets are on this screen. If I have a mouse connected to the RPi 4B while the Qt program is running, I can freely move the mouse cursor between the two screens. I've also had the same results building the same Qt library for Raspberry Pi OS Lite (Bullseye).
If you look at the screenshots at the bottom of my post you'll see that the TV appears to be combining both widgets and no widget is on the touchscreen. Also the widget assigned to the TV appears to be offset to the right due to the other widget assigned to the touchscreen. This results in a black region in the lower-left corner of the TV.
As another experiment I tried only displaying a widget on the touchscreen and that works as expected.
I am using the default overlay in /boot/config.txt: "dtoverlay=vc4-fkms-v3d". If I change it to: "dtoverlay=vc4-kms-v3d" then only one screen (TV) is detected.
If I remove that line completely from /boot/config.txt then I get the following error when executing the Qt program: "Could not find DRM device!".Here are the methods I've tried to use for moving a widget: QWidget::setGeometry and QWidget::move. These work fine in a windowing system like X11 on Ubuntu 20.04 with multiple monitors attached but not on the RPi 4B.
I've also checked the geometry of each widget after moving them to verify they reflect the proper screen position I set, which they do. I'm not seeing any errors when I execute the program.
Here is how I configured the build for Qt 5.15.2 on my Ubuntu 20.04 host machine. Note: I have to use Buster because there are other packages I need that are now deprecated in Bullseye. Build instruction references: https://github.com/UvinduW/Cross-Compiling-Qt-for-Raspberry-Pi-4 and https://www.interelectronix.com/qt-raspberry-pi-4.html
Toolchain (I tried this one and the one specified in the interelectronix.com article): https://releases.linaro.org/components/toolchain/binaries/7.4-2019.02/arm-linux-gnueabihf/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz
OS: Raspberry Pi OS Lite (Buster) https://downloads.raspberrypi.org/raspios_oldstable_lite_armhf/images/raspios_oldstable_lite_armhf-2022-09-26/2022-09-22-raspios-buster-armhf-lite.img.xz
Qt source: http://download.qt.io/archive/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.tar.xz
Modified files (the "rpi-qt" directory under my home directory contains all the files for building)
cp -R ~/rpi-qt/qt-everywhere-src-5.15.2/qtbase/mkspecs/linux-arm-gnueabi-g++ ~/rpi-qt/qt-everywhere-src-5.15.2/qtbase/mkspecs/linux-arm-gnueabihf-g++ sed -i -e 's/arm-linux-gnueabi-/arm-linux-gnueabihf-/g' ~/rpi-qt/qt-everywhere-src-5.15.2/qtbase/mkspecs/linux-arm-gnueabihf-g++/qmake.conf sed -i -e 's/\"main\"\: \"vc_dispmanx_display_open(0)\;\"/\"main\"\: \[\"vc_dispmanx_display_open(0)\;\"\, \"EGL_DISPMANX_WINDOW_T \*eglWindow \= new EGL_DISPMANX_WINDOW_T\;\"\]/g' ~/rpi-qt/qt-everywhere-src-5.15.2/qtbase/src/gui/configure.json
Build configuration:
./configure -release -opengl es2 -eglfs -device linux-rasp-pi4-v3d-g++ -device-option CROSS_COMPILE=~/rpi-qt/tools/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- -sysroot ~/rpi-qt/sysroot/ -prefix /usr/local/qt5.15 -extprefix ~/rpi-qt/qt5.15 -opensource -confirm-license -skip qtscript -skip qtwayland -skip qtwebengine -nomake tests -make libs -pkg-config -no-use-gold-linker -v -recheck
This is the program I used for testing multiple displays, this is all contained in main.cpp:
#include <QDebug> #include <QApplication> #include <QGuiApplication> #include <QVBoxLayout> #include <QLabel> #include <QScreen> int main(int argc, char *argv[]) { qputenv("QT_LOGGING_RULES", "qt.qpa.*=true"); qputenv("QSG_INFO", "1"); QApplication a(argc, argv); QList<QWidget *> widgets; qDebug() << "Number of screens:" <<QGuiApplication::screens().count(); for (int i = 0; i < QGuiApplication::screens().count(); ++i) { QWidget *w = new QWidget; w->setStyleSheet(QString("background-color: %1").arg(QColor::colorNames().at(i))); widgets.append(w); QLabel *lblDisplayNum = new QLabel; lblDisplayNum->setStyleSheet("font-size: 24px"); lblDisplayNum->setText(QString("Display %1").arg(i + 1)); QVBoxLayout *vlayout = new QVBoxLayout; vlayout->addWidget(lblDisplayNum); widgets[i]->setLayout(vlayout); widgets[i]->setGeometry(QGuiApplication::screens().at(i)->availableGeometry()); widgets[i]->show(); qDebug() << "Added widget with label:" << QString("Display %1").arg(i + 1) << ", geometry:" << widgets.at(i)->geometry() << ", background color:" << QColor::colorNames().at(i); } return a.exec(); }
This is the console output when running the above code: output.log
Here is what's shown on the TV (HDMI). The resolution is set to: 1360 x 768
and this is what's on the touchscreen (DSI). The resolution is: 800 x 480. You can see the mouse cursor in the top-left corner in this screenshot, which is shown while the Qt program is running. I can move the mouse cursor between screens:
How can I get each widget to show up on its assigned screen? Any ideas would be greatly appreciated.