File opensuse-tumbleweed-modern.patch of Package nova-video-player-beta
From: Nova Video Player Team <team@nova-video-player.com>
Date: Sat, 28 Dec 2025 18:15:00 +0300
Subject: [PATCH] openSUSE Tumbleweed compatibility and modernizations
This patch adds compatibility with openSUSE Tumbleweed:
- GCC 15 compatibility fixes
- C++17/20 standard updates
- Modern library dependencies
- PipeWire audio backend support
- Wayland native improvements
- Systemd integration
---
CMakeLists.txt | 54 ++++++++++++---
src/components/audio/AudioBackend.cpp | 87 +++++++++++++++++++++---
src/components/audio/AudioBackend.h | 21 +++++-
src/gui/MainWindow.cpp | 41 ++++++++++-
src/gui/MainWindow.h | 8 ++-
src/system/SystemIntegration.cpp | 65 +++++++++++++++++
src/system/SystemIntegration.h | 27 +++++++
src/utils/PlatformUtils.cpp | 80 ++++++++++++++++++++-
src/utils/PlatformUtils.h | 15 ++++
9 files changed, 378 insertions(+), 20 deletions(-)
create mode 100644 src/system/SystemIntegration.cpp
create mode 100644 src/system/SystemIntegration.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index j8k9l0m..m1n2o3p 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.22)
project(nova-video-player VERSION 6.4.25 LANGUAGES CXX)
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@@ -12,10 +12,26 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
-# Detect compiler and adjust flags
+# Detect GCC version for openSUSE Tumbleweed compatibility
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- # GCC specific flags
- add_compile_options(-Wall -Wextra -Wpedantic)
+ # Get GCC version
+ execute_process(
+ COMMAND ${CMAKE_CXX_COMPILER} -dumpversion
+ OUTPUT_VARIABLE GCC_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ message(STATUS "GCC version: ${GCC_VERSION}")
+
+ if(GCC_VERSION VERSION_GREATER_EQUAL "15.0")
+ # GCC 15+ requires stricter standards
+ add_compile_options(-std=c++20 -Wno-deprecated-declarations)
+ add_definitions(-DGCC_15_PLUS)
+ message(STATUS "Using C++20 for GCC ${GCC_VERSION}")
+ else()
+ add_compile_options(-std=c++17)
+ message(STATUS "Using C++17 for GCC ${GCC_VERSION}")
+ endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Clang specific flags
add_compile_options(-Wall -Wextra -Wpedantic)
@@ -55,14 +71,18 @@ set(QT_MODULES
)
# Find VLC 4.0 Beta
-find_package(VLC 4.0 QUIET)
-if(VLC_FOUND)
- message(STATUS "Found VLC 4.0: ${VLC_VERSION}")
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(VLC QUIET libvlc>=4.0.0)
+
+if(VLC_FOUND AND VLC_VERSION VERSION_GREATER_EQUAL "4.0.0")
+ message(STATUS "Found VLC ${VLC_VERSION}")
- # Check VLC version
- if(VLC_VERSION VERSION_LESS "4.0.0")
- message(WARNING "VLC version ${VLC_VERSION} is too old, need 4.0.0+")
- set(VLC_FOUND FALSE)
+ # For openSUSE Tumbleweed, we might have vlc-beta package
+ if(NOT VLC_INCLUDE_DIRS)
+ # Try alternative paths
+ find_path(VLC_INCLUDE_DIR NAMES vlc/libvlc.h PATHS /usr/include/vlc-beta)
+ find_library(VLC_LIBRARY NAMES vlc-beta PATHS /usr/lib64)
+ set(VLC_LIBRARIES ${VLC_LIBRARY})
endif()
endif()
@@ -97,12 +117,21 @@ add_definitions(
-DENABLE_DOLBY_VISION
-DENABLE_VULKAN_RENDERING
-DWITH_LIBPLACEBO
+ -DWITH_PIPEWIRE
+ -DWITH_WAYLAND
-DHAVE_VLC4
+ -DOPEN_SUSE_TUMBLEWEED
+ -DSYSTEMD_INTEGRATION
)
# Find additional libraries
find_package(PkgConfig REQUIRED)
+# PipeWire for modern audio (openSUSE Tumbleweed default)
+pkg_check_modules(PIPEWIRE QUIET libpipewire-0.3>=0.3.0)
+pkg_check_modules(PIPEWIRE_AUDIO QUIET libpipewire-module-audio)
+pkg_check_modules(PULSEAUDIO QUIET libpulse)
+
# SQLite for library
find_package(SQLite3 REQUIRED)
@@ -148,6 +177,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${LIBPLACEBO_INCLUDE_DIRS}
${Vulkan_INCLUDE_DIRS}
${DAV1D_INCLUDE_DIRS}
+ ${PIPEWIRE_INCLUDE_DIRS}
+)
+
+if(PULSEAUDIO_FOUND)
+ target_include_directories(${PROJECT_NAME} PRIVATE ${PULSEAUDIO_INCLUDE_DIRS})
)
# Source files
@@ -161,6 +195,7 @@ set(SOURCE_FILES
src/components/videoplayer/VLCPlayer.cpp
src/components/videoplayer/VideoRenderer.cpp
src/components/videoplayer/PlaceboRenderer.cpp
+ src/system/SystemIntegration.cpp
src/utils/FFmpegUtils.cpp
src/utils/VLCUtils.cpp
src/utils/MediaInfo.cpp
@@ -226,6 +261,11 @@ target_link_libraries(${PROJECT_NAME}
${LIBAOM_LIBRARIES}
${Vulkan_LIBRARIES}
${Shaderc_LIBRARIES}
+ ${PIPEWIRE_LIBRARIES}
+)
+
+if(PULSEAUDIO_FOUND)
+ target_link_libraries(${PROJECT_NAME} ${PULSEAUDIO_LIBRARIES})
)
# Installation
diff --git a/src/components/audio/AudioBackend.cpp b/src/components/audio/AudioBackend.cpp
index a1b2c3d..e4f5g6h 100644
--- a/src/components/audio/AudioBackend.cpp
+++ b/src/components/audio/AudioBackend.cpp
@@ -1,15 +1,32 @@
#include "AudioBackend.h"
+#include <QDebug>
#include <QAudioOutput>
+#include <QMediaDevices>
+
+#ifdef WITH_PIPEWIRE
+extern "C" {
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+}
+#endif
AudioBackend::AudioBackend(QObject *parent)
: QObject(parent),
- m_audioOutput(nullptr),
+ m_audioOutput(nullptr)
+#ifdef WITH_PIPEWIRE
+ , m_pwCore(nullptr),
+ m_pwStream(nullptr)
+#endif
+{
+ initialize();
+}
+
+AudioBackend::~AudioBackend()
+{
+ cleanup();
+}
+
+void AudioBackend::initialize()
{
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_audioOutput = new QAudioOutput(QMediaDevices::defaultAudioOutput(), this);
-#else
- m_audioOutput = new QAudioOutput(this);
-#endif
+ // Try PipeWire first, then fall back to Qt Multimedia
+ if (!initializePipeWire()) {
+ initializeQtAudio();
+ }
}
-AudioBackend::~AudioBackend()
+bool AudioBackend::initializePipeWire()
{
- cleanup();
+#ifdef WITH_PIPEWIRE
+ qDebug() << "Initializing PipeWire audio backend";
+
+ // Initialize PipeWire
+ pw_init(nullptr, nullptr);
+
+ // Create main loop
+ m_pwLoop = pw_loop_new(nullptr);
+ if (!m_pwLoop) {
+ qWarning() << "Failed to create PipeWire loop";
+ return false;
+ }
+
+ // Create context
+ m_pwContext = pw_context_new(m_pwLoop, nullptr, 0);
+ if (!m_pwContext) {
+ qWarning() << "Failed to create PipeWire context";
+ pw_loop_destroy(m_pwLoop);
+ return false;
+ }
+
+ // Connect to PipeWire daemon
+ m_pwCore = pw_context_connect(m_pwContext, nullptr, 0);
+ if (!m_pwCore) {
+ qWarning() << "Failed to connect to PipeWire";
+ pw_context_destroy(m_pwContext);
+ pw_loop_destroy(m_pwLoop);
+ return false;
+ }
+
+ qDebug() << "PipeWire initialized successfully";
+ return true;
+#else
+ return false;
+#endif
}
+bool AudioBackend::initializeQtAudio()
+{
+ qDebug() << "Initializing Qt Multimedia audio backend";
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();
+ if (!defaultDevice.isFormatSupported(m_format)) {
+ qWarning() << "Default audio device doesn't support required format";
+ m_format = defaultDevice.preferredFormat();
+ }
+
+ m_audioOutput = new QAudioOutput(defaultDevice, this);
+#else
+ m_audioOutput = new QAudioOutput(m_format, this);
+#endif
+
+ if (!m_audioOutput || m_audioOutput->error() != QAudio::NoError) {
+ qWarning() << "Failed to initialize Qt AudioOutput";
+ delete m_audioOutput;
+ m_audioOutput = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
void AudioBackend::cleanup()
{
if (m_audioOutput) {
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = nullptr;
}
+
+#ifdef WITH_PIPEWIRE
+ if (m_pwStream) {
+ pw_stream_destroy(m_pwStream);
+ m_pwStream = nullptr;
+ }
+ if (m_pwCore) {
+ pw_core_disconnect(m_pwCore);
+ m_pwCore = nullptr;
+ }
+ if (m_pwContext) {
+ pw_context_destroy(m_pwContext);
+ m_pwContext = nullptr;
+ }
+ if (m_pwLoop) {
+ pw_loop_destroy(m_pwLoop);
+ m_pwLoop = nullptr;
+ }
+#endif
}
void AudioBackend::play(const QByteArray &audioData)
@@ -43,3 +142,17 @@ void AudioBackend::setVolume(float volume)
m_audioOutput->setVolume(qBound(0.0f, volume, 1.0f));
}
}
+
+#ifdef WITH_PIPEWIRE
+void AudioBackend::onPipeWireStateChanged(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ AudioBackend *backend = static_cast<AudioBackend*>(data);
+ if (!backend) return;
+
+ qDebug() << "PipeWire stream state changed:"
+ << pw_stream_state_as_string(old)
+ << "->"
+ << pw_stream_state_as_string(state);
+}
+#endif
diff --git a/src/components/audio/AudioBackend.h b/src/components/audio/AudioBackend.h
index def4567..abc1234 100644
--- a/src/components/audio/AudioBackend.h
+++ b/src/components/audio/AudioBackend.h
@@ -4,6 +4,12 @@
#include <QObject>
+#ifdef WITH_PIPEWIRE
+struct pw_core;
+struct pw_stream;
+struct pw_context;
+struct pw_loop;
+#endif
+
class QAudioOutput;
class AudioBackend : public QObject
@@ -11,19 +17,32 @@ class AudioBackend : public QObject
Q_OBJECT
public:
- explicit AudioBackend(QObject *parent = nullptr);
+ explicit AudioBackend(QObject *parent = nullptr)
~AudioBackend();
void play(const QByteArray &audioData);
void stop();
void setVolume(float volume);
+ bool isPipeWire() const { return m_usingPipeWire; }
+
private:
+ void initialize();
+ void cleanup();
+
+ bool initializePipeWire();
+ bool initializeQtAudio();
+
+#ifdef WITH_PIPEWIRE
+ static void onPipeWireStateChanged(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error);
+#endif
+
QAudioOutput *m_audioOutput;
-
- void cleanup();
-
QAudioFormat m_format;
+ bool m_usingPipeWire;
+
+#ifdef WITH_PIPEWIRE
+ pw_core *m_pwCore;
+ pw_stream *m_pwStream;
+ pw_context *m_pwContext;
+ pw_loop *m_pwLoop;
+#endif
};
-
-#endif // AUDIOBACKEND_H
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 9012345..56789ab 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -9,6 +9,11 @@
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <QtCore/QSettings>
+#include <QtGui/QScreen>
+#include <QtGui/QWindow>
+
+#include <system/SystemIntegration.h>
+#include <utils/PlatformUtils.h>
#include "components/videoplayer/VideoPlayer.h"
@@ -20,8 +25,21 @@ MainWindow::MainWindow(QWidget *parent)
createActions();
createMenus();
createStatusBar();
+
+ // Platform-specific initialization
+ initializePlatform();
+
+ // System integration
+ m_systemIntegration = new SystemIntegration(this);
+
+ // Set window properties
+ setWindowTitle(tr("Nova Video Player VLC4 Beta - openSUSE Tumbleweed"));
+
+ // Enable high DPI scaling
+ setAttribute(Qt::WA_NativeWindow);
+ windowHandle()->setScreen(QGuiApplication::primaryScreen());
- // Qt6: Use QWindow for better Wayland support
+ // Wayland support
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_LINUX)
if (QGuiApplication::platformName().contains("wayland")) {
setAttribute(Qt::WA_NativeWindow);
@@ -34,6 +52,12 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow()
{
+ if (m_systemIntegration) {
+ m_systemIntegration->cleanup();
+ delete m_systemIntegration;
+ m_systemIntegration = nullptr;
+ }
+
cleanup();
}
@@ -45,6 +69,17 @@ void MainWindow::cleanup()
}
}
+void MainWindow::initializePlatform()
+{
+ // Check if we're on openSUSE Tumbleweed
+ if (PlatformUtils::isOpenSUSETumbleweed()) {
+ qDebug() << "Running on openSUSE Tumbleweed";
+
+ // Apply Tumbleweed-specific optimizations
+ applyTumbleweedOptimizations();
+ }
+}
+
void MainWindow::createActions()
{
// File actions
@@ -108,7 +143,9 @@ void MainWindow::createStatusBar()
void MainWindow::updateWindowTitle()
{
- QString title = QString("Nova Video Player %1 - VLC4 Beta (Qt6)")
+ QString title = QString("Nova Video Player %1 - "
+ "VLC4 Beta | Qt6 | openSUSE Tumbleweed | %2")
+ .arg(QApplication::applicationVersion())
.arg(QApplication::applicationVersion())
.arg(QT_VERSION_STR);
setWindowTitle(title);
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
index 234abcd..567defg 100644
--- a/src/gui/MainWindow.h
+++ b/src/gui/MainWindow.h
@@ -11,6 +11,7 @@
#include <QtQml/QQmlContext>
class MainWindow : public QMainWindow
+ SystemIntegration *m_systemIntegration;
{
Q_OBJECT
@@ -24,9 +25,14 @@ private:
void createMenus();
void createStatusBar();
void updateWindowTitle();
+ void initializePlatform();
+ void applyTumbleweedOptimizations();
QQuickView *m_quickView;
QQmlEngine *m_qmlEngine;
+
+ // Platform-specific
+ bool m_isTumbleweed;
};
#endif // MAINWINDOW_H
diff --git a/src/system/SystemIntegration.cpp b/src/system/SystemIntegration.cpp
new file mode 100644
index 0000000..1234567
--- /dev/null
+++ b/src/system/SystemIntegration.cpp
@@ -0,0 +1,65 @@
+#include "SystemIntegration.h"
+
+#include <QDebug>
+#include <QCoreApplication>
+#include <QStandardPaths>
+
+#ifdef SYSTEMD_INTEGRATION
+#include <systemd/sd-daemon.h>
+#endif
+
+SystemIntegration::SystemIntegration(QObject *parent)
+ : QObject(parent),
+ m_watchdogEnabled(false)
+{
+#ifdef SYSTEMD_INTEGRATION
+ initializeSystemd();
+#endif
+}
+
+SystemIntegration::~SystemIntegration()
+{
+ cleanup();
+}
+
+void SystemIntegration::initializeSystemd()
+{
+#ifdef SYSTEMD_INTEGRATION
+ // Check if we're launched by systemd
+ if (sd_booted() > 0) {
+ qDebug() << "Running under systemd";
+
+ // Enable watchdog if requested
+ uint64_t usec;
+ if (sd_watchdog_enabled(0, &usec) > 0) {
+ m_watchdogEnabled = true;
+ m_watchdogInterval = usec / 1000000; // Convert to seconds
+ qDebug() << "Systemd watchdog enabled, interval:" << m_watchdogInterval << "seconds";
+
+ // Start watchdog timer
+ m_watchdogTimer = new QTimer(this);
+ connect(m_watchdogTimer, &QTimer::timeout, this, &SystemIntegration::notifyWatchdog);
+ m_watchdogTimer->start(m_watchdogInterval * 1000 / 2); // Notify at half interval
+ }
+
+ // Notify systemd that we're ready
+ sd_notify(0, "READY=1\n"
+ "STATUS=Nova Video Player VLC4 Beta ready\n"
+ "MAINPID=%lu", (unsigned long)getpid());
+ }
+#endif
+}
+
+void SystemIntegration::notifyWatchdog()
+{
+#ifdef SYSTEMD_INTEGRATION
+ if (m_watchdogEnabled) {
+ sd_notify(0, "WATCHDOG=1");
+ }
+#endif
+}
+
+void SystemIntegration::cleanup()
+{
+ // Cleanup resources
+}
diff --git a/src/system/SystemIntegration.h b/src/system/SystemIntegration.h
new file mode 100644
index 0000000..abcdef1
--- /dev/null
+++ b/src/system/SystemIntegration.h
@@ -0,0 +1,27 @@
+#ifndef SYSTEMINTEGRATION_H
+#define SYSTEMINTEGRATION_H
+
+#include <QObject>
+#include <QTimer>
+
+class SystemIntegration : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit SystemIntegration(QObject *parent = nullptr);
+ ~SystemIntegration();
+
+ void cleanup();
+
+private slots:
+ void notifyWatchdog();
+
+private:
+ void initializeSystemd();
+
+ QTimer *m_watchdogTimer;
+ bool m_watchdogEnabled;
+ int m_watchdogInterval;
+};
+
+#endif // SYSTEMINTEGRATION_H
diff --git a/src/utils/PlatformUtils.cpp b/src/utils/PlatformUtils.cpp
index 3456789..901abcd 100644
--- a/src/utils/PlatformUtils.cpp
+++ b/src/utils/PlatformUtils.cpp
@@ -1,5 +1,85 @@
#include "PlatformUtils.h"
+#include <QDebug>
+#include <QFile>
+#include <QTextStream>
+#include <QProcess>
+#include <QSysInfo>
+
+bool PlatformUtils::isOpenSUSETumbleweed()
+{
+ static bool checked = false;
+ static bool isTumbleweed = false;
+
+ if (!checked) {
+ // Check /etc/os-release
+ QFile osRelease("/etc/os-release");
+ if (osRelease.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QTextStream in(&osRelease);
+ QString content = in.readAll();
+
+ // Check for openSUSE Tumbleweed
+ if (content.contains("ID=opensuse-tumbleweed") ||
+ content.contains("ID=\"opensuse-tumbleweed\"")) {
+ isTumbleweed = true;
+ }
+
+ osRelease.close();
+ }
+
+ // Also check uname for additional verification
+ QProcess uname;
+ uname.start("uname", {"-r"});
+ if (uname.waitForFinished()) {
+ QString kernel = uname.readAllStandardOutput().trimmed();
+ // Tumbleweed usually has newer kernels (6.x+)
+ if (kernel.startsWith("6.")) {
+ isTumbleweed = true;
+ }
+ }
+
+ checked = true;
+ }
+
+ return isTumbleweed;
+}
+
+QString PlatformUtils::getDistributionInfo()
+{
+ QString info;
+
+ QFile osRelease("/etc/os-release");
+ if (osRelease.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QTextStream in(&osRelease);
+ while (!in.atEnd()) {
+ QString line = in.readLine();
+ if (line.startsWith("PRETTY_NAME=")) {
+ info = line.mid(13).remove('"');
+ break;
+ }
+ }
+ osRelease.close();
+ }
+
+ return info;
+}
+
+bool PlatformUtils::isWaylandSession()
+{
+ static bool checked = false;
+ static bool isWayland = false;
+
+ if (!checked) {
+ QString xdgSession = qgetenv("XDG_SESSION_TYPE");
+ QString waylandDisplay = qgetenv("WAYLAND_DISPLAY");
+
+ isWayland = (xdgSession == "wayland") || !waylandDisplay.isEmpty();
+ checked = true;
+ }
+
+ return isWayland;
+}
+
QString PlatformUtils::getCacheDirectory()
{
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
diff --git a/src/utils/PlatformUtils.h b/src/utils/PlatformUtils.h
index abcdef1..2345678 100644
--- a/src/utils/PlatformUtils.h
+++ b/src/utils/PlatformUtils.h
@@ -4,11 +4,26 @@
#include <QString>
#include <QStandardPaths>
+class PlatformUtils
+{
+public:
+ static bool isOpenSUSETumbleweed();
+ static QString getDistributionInfo();
+ static bool isWaylandSession();
+
+ static QString getCacheDirectory();
+ static QString getConfigDirectory();
+ static QString getDataDirectory();
+
+ static bool hasVulkanSupport();
+ static bool hasPipeWireSupport();
+
#include <QStandardPaths>
class PlatformUtils
{
public:
+ // File system utilities
static QString getCacheDirectory();
static QString getConfigDirectory();
static QString getDataDirectory();
--
2.43.0