Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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: Headless smoke test
run: |
cd src/unix_build/bin
# Exit 124 (still running when the timeout fires) or 0 both mean it
# started without crashing; anything else is a failure.
set +e
QT_QPA_PLATFORM=offscreen timeout 10 ./SoftProjector
code=$?
set -e
if [ "$code" != "124" ] && [ "$code" != "0" ]; then
echo "SoftProjector exited with $code"
exit 1
fi

- 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:
- uses: actions/checkout@v4

- name: Install build dependencies
run: |
dnf -y install \
gcc-c++ make git 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
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 2 additions & 5 deletions 3rdparty/headers/qmediaplaylist.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
#define QMEDIAPLAYLIST_H

#include <QObject>

#include <qmediaenumdebug.h>
#include <QUrl>
#include <QList>

QT_BEGIN_NAMESPACE

Expand Down Expand Up @@ -89,7 +89,4 @@ public slots:

QT_END_NAMESPACE

Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode)
Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error)

#endif // QMEDIAPLAYLIST_H
4 changes: 2 additions & 2 deletions 3rdparty/sources/qmediaplaylist.cpp
Original file line number Diff line number Diff line change
@@ -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 <QCoreApplication>
Expand Down
2 changes: 1 addition & 1 deletion 3rdparty/sources/qmediaplaylist_p.cpp
Original file line number Diff line number Diff line change
@@ -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

Expand Down
72 changes: 72 additions & 0 deletions BUILDING_LINUX.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions packaging/linux/softprojector.desktop
Original file line number Diff line number Diff line change
@@ -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;
53 changes: 53 additions & 0 deletions packaging/rpm/softprojector.spec
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 27 additions & 0 deletions src/softProjector.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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
}
15 changes: 11 additions & 4 deletions src/sources/generalsettingwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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
}
19 changes: 18 additions & 1 deletion src/sources/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include <QMessageBox>
#include <QtSql>
#include <QStyleFactory>
#include <QStandardPaths>
#include <QFileInfo>
#include <QDir>
#include "../headers/softprojector.hpp"

// Definitions for database versions 'dbVer' numbers
Expand Down Expand Up @@ -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
Expand Down