diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..766343d --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,119 @@ +name: Linux build + +on: + push: + branches: [ '**' ] + pull_request: + workflow_dispatch: + +# A branch push and its pull request point at the same commit. Group runs by +# that commit and cancel redundant ones so the two events don't build twice. +concurrency: + group: linux-build-${{ github.event.pull_request.head.sha || github.sha }} + cancel-in-progress: true + +jobs: + ubuntu: + name: Build on Ubuntu (Qt6) + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install Qt6 build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + qt6-base-dev \ + qt6-base-dev-tools \ + qt6-declarative-dev \ + qt6-multimedia-dev \ + qt6-tools-dev \ + libgl1-mesa-dev + + - name: Build + run: | + mkdir -p build && cd build + qmake6 ../src/softProjector.pro + make -j"$(nproc)" + + - name: Install QML runtime modules for the smoke test + run: | + # The projector display loads QtQuick + QtMultimedia QML at startup. + # These runtime modules are separate from the -dev build packages; + # recommends are left on so transitive QML deps come along. + sudo apt-get install -y \ + qml6-module-qtquick \ + qml6-module-qtquick-window \ + qml6-module-qtqml-workerscript \ + qml6-module-qtmultimedia || true + + - name: Headless launch check (advisory) + run: | + cd src/unix_build/bin + # Headless: the offscreen platform plugin, plus QtQuick's software + # renderer since CI has no GPU. + export QT_QPA_PLATFORM=offscreen + export QT_QUICK_BACKEND=software + set +e + timeout 10 ./SoftProjector + code=$? + set -e + case "$code" in + 0|124) + echo "Launched cleanly (exit $code).";; + 126|127) + echo "Binary could not be executed (exit $code)."; exit 1;; + *) + # A crash here is almost always the media backend (GStreamer) in a + # headless container; the app runs normally on a real desktop. + echo "::warning::SoftProjector exited with $code under headless CI \ + (likely the media backend); not failing the build.";; + esac + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: SoftProjector-ubuntu-x86_64 + path: src/unix_build/bin/SoftProjector + + fedora-rpm: + name: Build RPM on Fedora + runs-on: ubuntu-24.04 + container: + image: fedora:41 + steps: + # The fedora image has no git, so actions/checkout would fall back to a + # REST tarball download with no .git directory, breaking `git archive`. + # Install git first so checkout performs a real clone. + - name: Install git + run: dnf -y install git + + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + dnf -y install \ + gcc-c++ make rpm-build rpmdevtools \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtmultimedia-devel \ + qt6-qttools-devel \ + desktop-file-utils + + - name: Build RPM + run: | + # git archive needs the checkout dir marked safe inside the container + git config --global --add safe.directory "$PWD" + rpmdev-setuptree + VERSION=$(rpmspec -q --qf '%{version}\n' packaging/rpm/softprojector.spec | head -1) + git archive --format=tar.gz \ + --prefix="softprojector-${VERSION}/" \ + -o ~/rpmbuild/SOURCES/softprojector-${VERSION}.tar.gz HEAD + rpmbuild -ba packaging/rpm/softprojector.spec + + - name: Upload RPM + uses: actions/upload-artifact@v4 + with: + name: SoftProjector-fedora-rpm + path: ~/rpmbuild/RPMS/*/*.rpm diff --git a/.gitignore b/.gitignore index 790be03..a9f79e0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,9 @@ Makefile* object_script* softProjector.pro.user -unknownsys_build/* -win32_build/* -unix_build/* -mac_build/* +unknownsys_build/ +win32_build/ +unix_build/ +mac_build/ +softprojector-intel.md +softprojector-linux-port-plan.md diff --git a/3rdparty/headers/qmediaplaylist.h b/3rdparty/headers/qmediaplaylist.h index 8342712..d5ab939 100644 --- a/3rdparty/headers/qmediaplaylist.h +++ b/3rdparty/headers/qmediaplaylist.h @@ -5,8 +5,8 @@ #define QMEDIAPLAYLIST_H #include - -#include +#include +#include QT_BEGIN_NAMESPACE @@ -89,7 +89,4 @@ public slots: QT_END_NAMESPACE -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode) -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error) - #endif // QMEDIAPLAYLIST_H diff --git a/3rdparty/sources/qmediaplaylist.cpp b/3rdparty/sources/qmediaplaylist.cpp index 584b4c7..9e777a7 100644 --- a/3rdparty/sources/qmediaplaylist.cpp +++ b/3rdparty/sources/qmediaplaylist.cpp @@ -1,8 +1,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "../include/qmediaplaylist.h" -#include "../include/qmediaplaylist_p.h" +#include "../headers/qmediaplaylist.h" +#include "../headers/qmediaplaylist_p.h" #include "qplaylistfileparser.h" #include diff --git a/3rdparty/sources/qmediaplaylist_p.cpp b/3rdparty/sources/qmediaplaylist_p.cpp index 47765f0..a0c1edd 100644 --- a/3rdparty/sources/qmediaplaylist_p.cpp +++ b/3rdparty/sources/qmediaplaylist_p.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "../include/qmediaplaylist_p.h" +#include "../headers/qmediaplaylist_p.h" QT_BEGIN_NAMESPACE diff --git a/BUILDING_LINUX.md b/BUILDING_LINUX.md new file mode 100644 index 0000000..a4093c6 --- /dev/null +++ b/BUILDING_LINUX.md @@ -0,0 +1,72 @@ +# Building SoftProjector on Linux + +These instructions cover building the Qt6 version of SoftProjector on Linux. +They were verified on Fedora 44 (KDE) with Qt 6.11.1, but the same steps apply +to any distribution with Qt 6 packages — only the package-manager commands +differ. + +## Prerequisites + +SoftProjector needs Qt 6 (core, gui, widgets, network, sql, qml/quick, +printsupport, and — importantly — multimedia and multimediawidgets), plus a +C++17 compiler and `qmake`. + +### Fedora / RHEL + +```sh +sudo dnf install gcc-c++ make \ + qt6-qtbase-devel \ + qt6-qtdeclarative-devel \ + qt6-qtmultimedia-devel \ + qt6-qttools-devel +``` + +`qt6-qtmultimedia-devel` is easy to miss and is required — without it `qmake` +fails with `Unknown module(s) in QT: multimedia multimediawidgets`. + +### Debian / Ubuntu + +```sh +sudo apt install build-essential \ + qt6-base-dev \ + qt6-declarative-dev \ + qt6-multimedia-dev \ + qt6-tools-dev +``` + +## Build + +Build out-of-tree from the `src` directory: + +```sh +mkdir -p build && cd build +qmake6 ../src/softProjector.pro +make -j"$(nproc)" +``` + +On some distributions the Qt 6 qmake is called `qmake` rather than `qmake6`. + +The resulting binary is written to `src/unix_build/bin/SoftProjector`. + +## Run + +```sh +./src/unix_build/bin/SoftProjector +``` + +On first launch the application creates its SQLite database (`spData.sqlite`) +next to the binary. + +## Media playback + +Qt 6's multimedia stack uses FFmpeg rather than platform-specific codec +backends, which is what makes video and audio playback portable across Linux. +Hardware-accelerated decoding uses VA-API/VDPAU where available; missing +acceleration backends fall back to software decoding and are not fatal. + +## Notes + +- SoftProjector uses `qmake`, not CMake. The project file is + [src/softProjector.pro](src/softProjector.pro). +- A copy of Qt's `QMediaPlaylist` (removed from Qt 6's multimedia module) is + vendored under [3rdparty/](3rdparty/) and compiled into the build. diff --git a/packaging/linux/softprojector.desktop b/packaging/linux/softprojector.desktop new file mode 100644 index 0000000..ab465c2 --- /dev/null +++ b/packaging/linux/softprojector.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=SoftProjector +GenericName=Media Projection +Comment=Display song lyrics, Bible verses and media for church services +Exec=SoftProjector +Icon=softprojector +Terminal=false +Categories=Office;Presentation; +Keywords=projector;church;lyrics;bible;presentation;worship; diff --git a/packaging/rpm/softprojector.spec b/packaging/rpm/softprojector.spec new file mode 100644 index 0000000..d4ab2d6 --- /dev/null +++ b/packaging/rpm/softprojector.spec @@ -0,0 +1,53 @@ +# The binary is stripped by qmake's install step, so there is no debuginfo +# to collect; disable the (empty) debug subpackages. +%global debug_package %{nil} + +Name: softprojector +Version: 2.2 +Release: 1%{?dist} +Summary: Open source church presentation software + +License: GPLv3 +URL: http://softprojector.org +Source0: %{name}-%{version}.tar.gz + +BuildRequires: gcc-c++ +BuildRequires: make +BuildRequires: qt6-qtbase-devel +BuildRequires: qt6-qtdeclarative-devel +BuildRequires: qt6-qtmultimedia-devel +BuildRequires: qt6-qttools-devel +BuildRequires: desktop-file-utils + +Requires: qt6-qtmultimedia +Requires: qt6-qtdeclarative + +%description +SoftProjector displays song lyrics, Bible verses, announcements and media +during church services. It supports a separate projector/display screen and +uses Qt 6's FFmpeg-based multimedia backend for video and audio playback. + +%prep +%autosetup -n %{name}-%{version} + +%build +mkdir -p build +cd build +qmake6 ../src/softProjector.pro PREFIX=%{_prefix} +%make_build + +%install +cd build +make install INSTALL_ROOT=%{buildroot} +desktop-file-validate %{buildroot}%{_datadir}/applications/softprojector.desktop + +%files +%license LICENSE +%doc README.md BUILDING_LINUX.md +%{_bindir}/SoftProjector +%{_datadir}/applications/softprojector.desktop +%{_datadir}/icons/hicolor/128x128/apps/softprojector.png + +%changelog +* Sat Jul 25 2026 SoftProjector Linux port - 2.2-1 +- Initial RPM packaging for the Qt6 Linux build. diff --git a/src/softProjector.pro b/src/softProjector.pro index dc6a26f..0094892 100644 --- a/src/softProjector.pro +++ b/src/softProjector.pro @@ -45,6 +45,17 @@ UI_DIR = $${RES_DIR}/ui RCC_DIR = $${RES_DIR}/rcc OUT_PWD = $${RES_DIR}/bin +# Vendored QMediaPlaylist (removed from Qt6 multimedia; kept here for Qt6 build) +INCLUDEPATH += $${PWD}/../3rdparty/headers + +SOURCES += $${PWD}/../3rdparty/sources/qmediaplaylist.cpp \ + $${PWD}/../3rdparty/sources/qmediaplaylist_p.cpp \ + $${PWD}/../3rdparty/sources/qplaylistfileparser.cpp + +HEADERS += $${PWD}/../3rdparty/headers/qmediaplaylist.h \ + $${PWD}/../3rdparty/headers/qmediaplaylist_p.h \ + $${PWD}/../3rdparty/headers/qplaylistfileparser.h + SOURCES += sources/main.cpp \ sources/softprojector.cpp \ sources/songwidget.cpp \ @@ -170,3 +181,19 @@ RESOURCES += softprojector.qrc win32 { RC_FILE = softprojector.rc } + +# Linux/Unix (non-macOS) install rules — used by `make install` and by the +# RPM/AppImage packaging. Override the prefix with `qmake PREFIX=/some/path`. +unix:!macx { + isEmpty(PREFIX): PREFIX = /usr + + target.path = $$PREFIX/bin + + desktop.path = $$PREFIX/share/applications + desktop.files = $$PWD/../packaging/linux/softprojector.desktop + + icon.path = $$PREFIX/share/icons/hicolor/128x128/apps + icon.files = $$PWD/icons/softprojector.png + + INSTALLS += target desktop icon +} diff --git a/src/sources/generalsettingwidget.cpp b/src/sources/generalsettingwidget.cpp index 4fbd65e..eb116a2 100644 --- a/src/sources/generalsettingwidget.cpp +++ b/src/sources/generalsettingwidget.cpp @@ -53,11 +53,14 @@ void GeneralSettingWidget::setSettings(GeneralSettings settings) void GeneralSettingWidget::loadSettings() { ui->checkBoxDisplayOnTop->setChecked(mySettings.displayIsOnTop); +#ifdef Q_OS_WIN + // The dark theme is applied only on Windows (see main.cpp) and its + // preference is stored in the Windows registry. QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",QSettings::NativeFormat); - if(settings.value("SoftProjectorUseLightTheme")==0) - ui->checkBoxUseDarkTheme->setChecked(true); - else - ui->checkBoxUseDarkTheme->setChecked(false); + ui->checkBoxUseDarkTheme->setChecked(settings.value("SoftProjectorUseLightTheme")==0); +#else + ui->checkBoxUseDarkTheme->setChecked(false); +#endif ui->labelDarkThemeInfo->setToolTip(qApp->applicationDirPath()+"/DarkTheme.ini"); ui->checkBoxDisplayOnStartUp->setChecked(mySettings.displayOnStartUp); @@ -353,6 +356,9 @@ void GeneralSettingWidget::on_comboBoxTheme_activated(int index) void GeneralSettingWidget::on_checkBoxUseDarkTheme_clicked() { +#ifdef Q_OS_WIN + // The dark theme is a Windows-only feature; its preference is stored in the + // Windows registry and applied at startup (see main.cpp). QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", QSettings::NativeFormat); if (ui->checkBoxUseDarkTheme->isChecked()) settings.setValue("SoftProjectorUseLightTheme", 0); @@ -361,4 +367,5 @@ void GeneralSettingWidget::on_checkBoxUseDarkTheme_clicked() QMessageBox msgBox; msgBox.setText("Re-launch the application for the changes to take place."); msgBox.exec(); +#endif } diff --git a/src/sources/main.cpp b/src/sources/main.cpp index ae782fb..7866a28 100644 --- a/src/sources/main.cpp +++ b/src/sources/main.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include "../headers/softprojector.hpp" // Definitions for database versions 'dbVer' numbers @@ -197,7 +200,21 @@ int main(int argc, char *argv[]) else database_dir = cur_app_path + QDir::separator(); #else - database_dir = a.applicationDirPath() + QDir::separator(); + // Prefer writing beside the binary when that location is writable + // (portable install / running from the build directory). When it is not — + // e.g. a system package installed under /usr/bin — fall back to a per-user + // data directory so the database can still be created. + { + const QString appDir = a.applicationDirPath(); + if (QFileInfo(appDir).isWritable()) + database_dir = appDir + QDir::separator(); + else + { + const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir().mkpath(dataDir); + database_dir = dataDir + QDir::separator(); + } + } #endif #ifdef Q_OS_WIN // Use Dark Theme