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
openSUSE Build Service is sponsored by