Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ffado for openSUSE:Factory checked 
in at 2026-10-18 19:22:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ffado (Old)
 and      /work/SRC/openSUSE:Factory/.ffado.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ffado"

Sun Oct 18 19:22:38 2026 rev:49 rq:1385676 version:2.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/ffado/ffado.changes      2025-06-06 
22:35:31.970111701 +0200
+++ /work/SRC/openSUSE:Factory/.ffado.new.3698736/ffado.changes 2026-10-18 
19:22:56.796820715 +0200
@@ -1,0 +2,12 @@
+Sat Oct 17 17:06:53 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 2.5.0:
+  * ffado-mixer: visual and functional improvements; window geometry
+    and state are restored on start, device state can auto-restore.
+  * Fix crossbar router state restoration and setting restoration
+    with multiple devices present.
+  * Support multiple M-Audio ProFire 2626 devices simultaneously
+    via unique GUIDs, with corrected routing tables.
+  * Python type-mismatch and device-nickname fixes.
+
+-------------------------------------------------------------------

Old:
----
  libffado-2.4.9.tgz

New:
----
  libffado-2.5.0.tgz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ffado.spec ++++++
--- /var/tmp/diff_new_pack.S3rMw3/_old  2026-10-18 19:22:58.191878485 +0200
+++ /var/tmp/diff_new_pack.S3rMw3/_new  2026-10-18 19:22:58.197878733 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package ffado
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -26,13 +26,12 @@
 %define sover 2
 %define tname libffado
 Name:           ffado%{mflavor}
-Version:        2.4.9
+Version:        2.5.0
 Release:        0
 Summary:        FireWire 1394 support for audio devices
 License:        GPL-2.0-or-later
-Group:          Productivity/Multimedia/Other
-URL:            http://www.ffado.org/
-Source0:        http://www.ffado.org/files/%{tname}-%{version}.tgz
+URL:            https://www.ffado.org/
+Source0:        https://www.ffado.org/files/%{tname}-%{version}.tgz
 Source1:        baselibs.conf
 # No current date and time allowed.
 Patch0:         libffado-date_time.patch
@@ -41,7 +40,7 @@
 Patch5:         reproducible.patch
 BuildRequires:  fdupes
 BuildRequires:  gcc-c++
-BuildRequires:  pkg-config
+BuildRequires:  pkgconfig
 BuildRequires:  pkgconfig(alsa)
 BuildRequires:  pkgconfig(dbus-c++-1)
 BuildRequires:  pkgconfig(dbus-python)
@@ -69,11 +68,11 @@
 BuildRequires:  xdg-utils
 BuildRequires:  xorg-x11-fonts
 BuildRequires:  pkgconfig(libffado) = %{version}
+Requires:       ffado = %{version}
+BuildArch:      noarch
 %if 0%{?suse_version} == 1600 && !0%{?is_opensuse}
 ExclusiveArch:  do_not_build
 %endif
-Requires:       ffado = %{version}
-BuildArch:      noarch
 %endif
 
 %description
@@ -95,7 +94,6 @@
 
 %package -n ffado-devel
 Summary:        Development files for ffado
-Group:          Development/Libraries/C and C++
 Requires:       glibc-devel
 Requires:       libffado%{sover} = %{version}
 Requires:       pkgconfig(dbus-c++-1)
@@ -109,7 +107,6 @@
 
 %package -n libffado%{sover}
 Summary:        FireWire 1394 support for audio devices
-Group:          System/Libraries
 
 %description -n libffado%{sover}
 This package provides the libffado shared library that
@@ -128,19 +125,19 @@
 %prep
 %autosetup -n %{tname}-%{version} -p0
 
-for i in `grep -rl "/usr/bin/env python"`;do sed -i 
'1s/^#!.*/#!\/usr\/bin\/python3/' ${i} ;done
-for i in `grep -rl "/usr/bin/python"`;do sed -i 
'1s/^#!.*/#!\/usr\/bin\/python3/' ${i} ;done
+for i in `grep -rl "%{_bindir}/env python"`;do sed -i 
'1s/^#!.*/#!\/usr\/bin\/python3/' ${i} ;done
+for i in `grep -rl "%{_bindir}/python"`;do sed -i 
'1s/^#!.*/#!\/usr\/bin\/python3/' ${i} ;done
 
 %build
 export EXTRA_FLAGS="-Wno-deprecated-declarations -fpermissive --std=gnu++11"
 %if 0%{?gcc_version} > 5
 export EXTRA_FLAGS="${EXTRA_FLAGS}  -Werror=date-time"
 %endif
-%ifarch %arm
+%ifarch %{arm}
 export EXTRA_FLAGS="${EXTRA_FLAGS} -fPIC"
 %endif
 #export DBUS1_FLAGS="$DBUS1_FLAGS -lpthread"
-scons %{_smp_mflags} \
+scons %{?_smp_mflags} \
   PREFIX=%{_prefix} \
   LIBDIR=%{_libdir} \
   MANDIR=%{_mandir} \
@@ -172,7 +169,7 @@
 mkdir -p %{buildroot}%{python3_sitelib}
 # WARNING: the install scons statement must be identical to the build one 
otherwise a complete rebuild is executed.
 export EXTRA_FLAGS="-Wno-deprecated-declarations -fpermissive --std=gnu++11"
-%ifarch %arm
+%ifarch %{arm}
 export EXTRA_FLAGS="${EXTRA_FLAGS} -fPIC"
 %endif
 #export DBUS1_FLAGS="$DBUS1_FLAGS -lpthread"
@@ -209,7 +206,7 @@
 %endif
 
 %if "%{flavor}" == "mixer"
-rm -Rfv %{buildroot}/%{_libdir}/%tname.* %{buildroot}/%{_includedir}/libffado \
+rm -Rfv %{buildroot}/%{_libdir}/%{tname}.* 
%{buildroot}/%{_includedir}/libffado \
        %{buildroot}/%{_libdir}/pkgconfig
 
 # Remove the useless udev rules and man pages on the mixer package
@@ -230,7 +227,7 @@
 %endif
 
 %fdupes -s %{buildroot}%{_datadir}
-%python3_fix_shebang
+%{python3_fix_shebang}
 
 %ldconfig_scriptlets -n libffado%{sover}
 
@@ -268,11 +265,11 @@
 %{_datadir}/dbus-1/services/org.ffado.Control.service
 %{_bindir}/ffado*
 %{_datadir}/%{tname}
-%{_mandir}/man1/ffado-bridgeco-downloader.1.gz
-%{_mandir}/man1/ffado-dbus-server.1.gz
-%{_mandir}/man1/ffado-diag.1.gz
-%{_mandir}/man1/ffado-dice-firmware.1.gz
-%{_mandir}/man1/ffado-fireworks-downloader.1.gz
+%{_mandir}/man1/ffado-bridgeco-downloader.1%{?ext_man}
+%{_mandir}/man1/ffado-dbus-server.1%{?ext_man}
+%{_mandir}/man1/ffado-diag.1%{?ext_man}
+%{_mandir}/man1/ffado-dice-firmware.1%{?ext_man}
+%{_mandir}/man1/ffado-fireworks-downloader.1%{?ext_man}
 %endif
 %if "%{flavor}" == "mixer"
 %{_bindir}/ffado-mixer
@@ -280,7 +277,7 @@
 %{_datadir}/icons/*
 %{_datadir}/pixmaps/ffadomixer.png
 %{_datadir}/applications/*
-%{python3_sitelib}/*
+%{python3_sitelib}/ffado
 %{_datadir}/metainfo/org.ffado.FfadoMixer.metainfo.xml
 %endif
 

++++++ libffado-2.4.9.tgz -> libffado-2.5.0.tgz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libffado-2.4.9/SConstruct 
new/libffado-2.5.0/SConstruct
--- old/libffado-2.4.9/SConstruct       2024-06-26 11:44:00.000000000 +0200
+++ new/libffado-2.5.0/SConstruct       2026-01-06 11:31:32.000000000 +0100
@@ -25,7 +25,7 @@
 from __future__ import print_function
 
 FFADO_API_VERSION = "9"
-FFADO_VERSION="2.4.9"
+FFADO_VERSION="2.5.0"
 
 from subprocess import Popen, PIPE, check_output
 import os
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libffado-2.4.9/src/dice/maudio/profire_2626.cpp 
new/libffado-2.5.0/src/dice/maudio/profire_2626.cpp
--- old/libffado-2.4.9/src/dice/maudio/profire_2626.cpp 2022-02-25 
13:25:10.000000000 +0100
+++ new/libffado-2.5.0/src/dice/maudio/profire_2626.cpp 2025-08-15 
12:07:41.000000000 +0200
@@ -53,10 +53,10 @@
 //
 void Profire2626::Profire2626EAP::setupSources_low()
 {
+    addSource("SPDIF/In", 0, 2, eRS_AES, 1);
+    addSource("ADAT-A/In", 0, 8, eRS_ADAT, 1);
+    addSource("ADAT-B/In", 8, 8, eRS_ADAT, 1);
     addSource("Mic/Line/In", 0, 8, eRS_InS1, 1);
-    addSource("ADAT A/In", 0, 8, eRS_ADAT, 1);
-    addSource("ADAT B/In", 9, 8, eRS_ADAT, 1);
-    addSource("SPDIF/In", 14, 2, eRS_AES, 1);
     addSource("Mixer/Out", 0, 16, eRS_Mixer, 1);
     addSource("1394/In", 0, 10, eRS_ARX0, 1);
     addSource("1394/In", 0, 16, eRS_ARX1, 11);
@@ -65,23 +65,22 @@
 
 void Profire2626::Profire2626EAP::setupDestinations_low()
 {
-    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
-    addDestination("ADAT A/Out", 0, 8, eRD_ADAT, 1);
-    addDestination("ADAT B/Out", 8, 8, eRD_ADAT, 1);
     addDestination("SPDIF/Out", 0, 2, eRD_AES, 1);
+    addDestination("ADAT-A/Out", 0, 8, eRD_ADAT, 1);
+    addDestination("ADAT-B/Out", 8, 8, eRD_ADAT, 1);
+    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
     addDestination("Mixer/In", 0, 16, eRD_Mixer0, 1);
     addDestination("Mixer/In", 0, 2, eRD_Mixer1, 17);
     addDestination("1394/Out", 0, 10, eRD_ATX0, 1);
     addDestination("1394/Out", 0, 16, eRD_ATX1, 11);
-    addDestination("Mute", 0, 1, eRD_Muted);
 }
 
 void Profire2626::Profire2626EAP::setupSources_mid()
 {
+    addSource("SPDIF/In", 0, 2, eRS_AES, 1);
     addSource("Mic/Line/In", 0, 8, eRS_InS1, 1);
-    addSource("ADAT A/In", 0, 4, eRS_ADAT, 1);
-    addSource("ADAT B/In", 4, 4, eRS_ADAT, 1);
-    addSource("SPDIF/In", 14, 2, eRS_AES, 1);
+    addSource("ADAT-A/In", 0, 4, eRS_ADAT, 1);
+    addSource("ADAT-B/In", 4, 4, eRS_ADAT, 1);
     addSource("Mixer/Out", 0, 16, eRS_Mixer, 1);
     addSource("1394/In", 0, 10, eRS_ARX0, 1);
     addSource("1394/In", 0, 8, eRS_ARX1, 11);
@@ -90,23 +89,22 @@
 
 void Profire2626::Profire2626EAP::setupDestinations_mid()
 {
-    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
-    addDestination("ADAT A/Out", 0, 4, eRD_ADAT, 1);
-    addDestination("ADAT B/Out", 4, 4, eRD_ADAT, 1);
     addDestination("SPDIF/Out", 0, 2, eRD_AES, 1);
+    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
+    addDestination("ADAT-A/Out", 0, 4, eRD_ADAT, 1);
+    addDestination("ADAT-B/Out", 4, 4, eRD_ADAT, 1);
     addDestination("Mixer/In", 0, 16, eRD_Mixer0, 1);
     addDestination("Mixer/In", 0, 2, eRD_Mixer1, 17);
     addDestination("1394/Out", 0, 10, eRD_ATX0, 1);
     addDestination("1394/Out", 0, 8, eRD_ATX1, 11);
-    addDestination("Mute", 0, 1, eRD_Muted);
 }
 
 void Profire2626::Profire2626EAP::setupSources_high()
 {
+    addSource("SPDIF/In", 0, 2, eRS_AES, 1);
     addSource("Mic/Line/In", 0, 8, eRS_InS1, 1);
-    addSource("ADAT A/In", 0, 2, eRS_ADAT, 1);
-    addSource("ADAT B/In", 2, 2, eRS_ADAT, 1);
-    addSource("SPDIF/In", 14, 2, eRS_AES, 1);
+    addSource("ADAT-A/In", 0, 2, eRS_ADAT, 1);
+    addSource("ADAT-B/In", 2, 2, eRS_ADAT, 1);
     addSource("Mixer/Out", 0, 16, eRS_Mixer, 1);
     addSource("1394/In", 0, 8, eRS_ARX0, 1);
     addSource("1394/In", 0, 6, eRS_ARX1, 9);
@@ -115,15 +113,14 @@
 
 void Profire2626::Profire2626EAP::setupDestinations_high()
 {
-    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
-    addDestination("ADAT A/Out", 0, 2, eRD_ADAT, 1);
-    addDestination("ADAT B/Out", 2, 2, eRD_ADAT, 1);
     addDestination("SPDIF/Out", 0, 2, eRD_AES, 1);
+    addDestination("Line/Out", 0, 8, eRD_InS1, 1);
+    addDestination("ADAT-A/Out", 0, 2, eRD_ADAT, 1);
+    addDestination("ADAT-B/Out", 2, 2, eRD_ADAT, 1);
     addDestination("Mixer/In", 0, 16, eRD_Mixer0, 1);
     addDestination("Mixer/In", 0, 2, eRD_Mixer1, 17);
     addDestination("1394/Out", 0, 8, eRD_ATX0, 1);
     addDestination("1394/Out", 0, 6, eRD_ATX1, 9);
-    addDestination("Mute", 0, 1, eRD_Muted);
 }
 
 /**
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libffado-2.4.9/src/libieee1394/configrom.cpp 
new/libffado-2.5.0/src/libieee1394/configrom.cpp
--- old/libffado-2.4.9/src/libieee1394/configrom.cpp    2022-02-25 
13:25:10.000000000 +0100
+++ new/libffado-2.5.0/src/libieee1394/configrom.cpp    2025-04-16 
14:53:44.000000000 +0200
@@ -207,6 +207,16 @@
     m_guid = ((u_int64_t)CSR1212_BE32_TO_CPU(m_csr->bus_info_data[3]) << 32)
              | CSR1212_BE32_TO_CPU(m_csr->bus_info_data[4]);
 
+    // Workaround: every MAudio ProFire 2626 has the same GUID.  Try to make
+    // them unique by adding the node ID so FFADO can handle multiple ProFire
+    // 2626s if connected.
+    if (m_guid == 0x000d6c0404000002) {
+        debugOutput(DEBUG_LEVEL_INFO,
+                    "Making ProFire 2626 GUID unique: adding bus node ID (%d)",
+                    m_nodeId);
+        m_guid += m_nodeId;
+    }
+
     if ( m_vendorNameKv ) {
         csr1212_release_keyval( m_vendorNameKv );
         m_vendorNameKv = 0;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libffado-2.4.9/support/mixer-qt4/ffado/ffadowindow.py 
new/libffado-2.5.0/support/mixer-qt4/ffado/ffadowindow.py
--- old/libffado-2.4.9/support/mixer-qt4/ffado/ffadowindow.py   2022-02-25 
13:25:10.000000000 +0100
+++ new/libffado-2.5.0/support/mixer-qt4/ffado/ffadowindow.py   2025-10-01 
13:59:12.000000000 +0200
@@ -49,25 +49,116 @@
         self.layout.setContentsMargins( 50, 10, 50, 10 )
         self.layout.addWidget(self.label, 0, 0, Qt.AlignHCenter|Qt.AlignBottom)
         self.layout.addWidget(self.button, 1, 0, Qt.AlignHCenter|Qt.AlignTop)
+ 
+class PreferencesDialog(QDialog):
+    def __init__(self, parent=None):
+        super(PreferencesDialog, self).__init__(parent)
+        self.setWindowTitle("Preferences")
+        self.settings = QtCore.QSettings(parent)
+        self.parent_window = parent
+        layout = QVBoxLayout(self)
+
+        # Show log window checkbox
+        self.log_checkbox = QCheckBox("Show log window", self)
+        show_log = self.settings.value("window/show_log", True, type=bool)
+        self.log_checkbox.setChecked(show_log)
+        layout.addWidget(self.log_checkbox)
+        self.log_checkbox.toggled.connect(self.on_log_checkbox_toggled)
+
+        # Load device settings on startup
+        self.load_settings_checkbox = QCheckBox("Load device settings on 
startup", self)
+        load_settings = self.settings.value("window/load_settings_on_startup", 
False, type=bool)
+        self.load_settings_checkbox.setChecked(load_settings)
+        layout.addWidget(self.load_settings_checkbox)
+        
self.load_settings_checkbox.toggled.connect(self.on_load_settings_toggled)
+
+        # File path field and button
+        self.file_path_layout = QHBoxLayout()
+        self.file_path_edit = QLineEdit(self)
+        self.load_settings_label = QLabel("Choose path to saved file.", self)
+        self.file_path_layout.addWidget(self.load_settings_label)
+        saved_path = self.settings.value("window/device_settings_path", "", 
type=str)
+        self.file_path_edit.setText(saved_path)
+        self.file_dialog_btn = QPushButton("...", self)
+        self.file_dialog_btn.clicked.connect(self.select_file_dialog)
+        self.file_path_layout.addWidget(self.file_path_edit)
+        self.file_path_layout.addWidget(self.file_dialog_btn)
+        layout.addLayout(self.file_path_layout)
+
+        # Show/hide file path widgets based on checkbox
+        self.load_settings_label.setVisible(load_settings)
+        self.file_path_edit.setVisible(load_settings)
+        self.file_dialog_btn.setVisible(load_settings) 
+
+        buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | 
QDialogButtonBox.Cancel, self)
+        buttonBox.accepted.connect(self.accept)
+        buttonBox.rejected.connect(self.reject)
+        layout.addWidget(buttonBox)
+
+    def get_log_checkbox_state(self):
+        return self.log_checkbox.isChecked()
+
+    def on_log_checkbox_toggled(self, checked):
+        # Update dock visibility immediately
+        if self.parent_window is not None:
+            self.parent_window.logger_dock.setVisible(checked)
+            
+    def on_load_settings_toggled(self, checked):
+        self.load_settings_label.setVisible(checked)
+        self.file_path_edit.setVisible(checked)
+        self.file_dialog_btn.setVisible(checked)
+
+    def select_file_dialog(self):
+        path, _ = QFileDialog.getOpenFileName(self, "Select Device Settings 
File")
+        if path:
+            self.file_path_edit.setText(path)
+
+    def get_load_settings_state(self):
+        return self.load_settings_checkbox.isChecked()
+
+    def get_device_settings_path(self):
+        return self.file_path_edit.text() 
+
+    def accept(self):
+        self.settings.setValue("window/show_log", 
self.log_checkbox.isChecked())
+        super(PreferencesDialog, self).accept()
 
 class FFADOWindow(QMainWindow):
     def __init__(self, parent):
         QMainWindow.__init__(self, parent)
 
         self.textlogger = QTextLogger(self)
-        dock = QDockWidget("Log Messages",self)
-        dock.setWidget(self.textlogger.textedit)
+        self.logger_dock = QDockWidget("Log Messages",self)
+        self.logger_dock.setWidget(self.textlogger.textedit)
         logging.getLogger('').addHandler(self.textlogger)
-        self.addDockWidget(Qt.BottomDockWidgetArea, dock)
+        self.addDockWidget(Qt.BottomDockWidgetArea, self.logger_dock)
 
         self.statuslogger = QStatusLogger(self, self.statusBar(), 20)
         logging.getLogger('').addHandler(self.statuslogger)
 
         self.settings = QtCore.QSettings(self)
+ 
+        # Show/hide log dock based on settings
+        show_log = self.settings.value("window/show_log", True, type=bool)
+        self.logger_dock.setVisible(show_log)
+
+        # Restore window geometry and position
+        geometry = self.settings.value("window/geometry")
+        if geometry is not None:
+            self.restoreGeometry(geometry)
+        window_state = self.settings.value("window/state")
+        if window_state is not None:
+            self.restoreState(window_state)
 
         self.manager = PanelManager(self)
         self.manager.connectionLost.connect(self.connectToDBUS)
 
+        # Load device settings on startup if enabled and path is set
+        load_settings = self.settings.value("window/load_settings_on_startup", 
False, type=bool)
+        device_settings_path = 
self.settings.value("window/device_settings_path", "", type=str)
+        if load_settings and device_settings_path:
+            self.manager.readSettings(device_settings_path)
+
         filemenu = self.menuBar().addMenu("&File")
         self.openaction = QAction(QIcon.fromTheme("document-open"),"&Open", 
self)
         self.openaction.setShortcut(self.tr("Ctrl+O"))
@@ -86,6 +177,12 @@
 
         self.editmenu = self.menuBar().addMenu("&View")
 
+        self.setPreferencesAction = QtWidgets.QAction(parent)
+        self.setPreferencesAction.setText("Preferences")
+        self.setPreferencesAction.setObjectName("setPreferencesAction")
+        self.setPreferencesAction.triggered.connect(self.showPreferencesDialog)
+        self.editmenu.addAction(self.setPreferencesAction)
+
         self.thememenu = self.editmenu.addMenu("Theme")
         themes = QStyleFactory.keys()
         self.menuTheme = {}
@@ -139,6 +236,18 @@
         log.info( "Starting up" )
 
         QTimer.singleShot( 1, self.tryStartDBUSServer )
+        QTimer.singleShot( 2 * POLL_SLEEP_TIME_MSEC, self.loadSettings )
+
+    def loadSettings(self):
+        # Load device settings on startup if enabled and path is set
+        load_settings = self.settings.value("window/load_settings_on_startup", 
False, type=bool)
+        device_settings_path = 
self.settings.value("window/device_settings_path", "", type=str)
+        if load_settings and device_settings_path:
+            self.manager.readSettings(device_settings_path) 
+
+    def showPreferencesDialog(self):
+        dialog = PreferencesDialog(self)
+        dialog.exec_()
 
     def __del__(self):
         log.info("__del__")
@@ -157,6 +266,9 @@
                 self.settings.setValue("window/theme", theme.__str__())
 
     def closeEvent(self, event):
+        # Save window geometry and state
+        self.settings.setValue("window/geometry", self.saveGeometry())
+        self.settings.setValue("window/state", self.saveState())        
         log.info("closeEvent()")
         event.accept()
 
@@ -199,7 +311,7 @@
 
 <p><a href="http://ffado.org";>FFADO</a> is the new approach to have FireWire 
audio on Linux.</p>
 
-        <p>&copy; 2006-2021 by the FFADO developers<br />ffado is licensed 
under the GPLv3, for the full license text see <a 
href="http://www.gnu.org/licenses/";>www.gnu.org/licenses</a> or the LICENSE.* 
files shipped with ffado.</p>
+        <p>&copy; 2006-2025 by the FFADO developers<br />ffado is licensed 
under the GPLv3, for the full license text see <a 
href="http://www.gnu.org/licenses/";>www.gnu.org/licenses</a> or the LICENSE.* 
files shipped with ffado.</p>
 
 <p>FFADO developers are:<ul>
 <li>Pieter Palmers
@@ -223,7 +335,7 @@
         lib = ctypes.cdll.LoadLibrary('libffado.so')
         func = ctypes.CFUNCTYPE(ctypes.c_char_p)
         ffado_get_version = func(('ffado_get_version', lib))
-        return ffado_get_version()
+        return str(ffado_get_version())
     except:
         return "libffado"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libffado-2.4.9/support/mixer-qt4/ffado/mixer/globalmixer.py 
new/libffado-2.5.0/support/mixer-qt4/ffado/mixer/globalmixer.py
--- old/libffado-2.4.9/support/mixer-qt4/ffado/mixer/globalmixer.py     
2022-02-25 13:25:10.000000000 +0100
+++ new/libffado-2.5.0/support/mixer-qt4/ffado/mixer/globalmixer.py     
2025-08-27 15:39:49.000000000 +0200
@@ -45,24 +45,38 @@
 
     @pyqtSlot(int)
     def on_clocksource_activated( self, clock ):
-        #log.debug("updateClockSource( " + str(clock) + " )")
-        if self.clockselect.canChangeValue():
-            self.clockselect.select( clock )
-        else:
+        log.debug("on_clocksource_activated( " + str(clock) + " )")
+        # get current value from device
+        selected = self.clockselect.selected()
+
+        # is streaming active?
+        canChange = self.clockselect.canChangeValue()
+        
+        # only throw message and return if streaming is active and the
+        # current clock is not the same as the new setting
+        if not canChange and clock != selected:
             msg = QMessageBox()
             msg.question( msg, "Error", \
                 "<qt>Clock source change not permitted. Is streaming 
active?</qt>", \
                 QMessageBox.Ok )
             self.clocksource.setEnabled(False)
             return
-
-        selected = self.clockselect.selected()
-        if selected != clock:
-            clockname = self.clockselect.getEnumLabel( clock )
-            msg = QMessageBox()
-            msg.question( msg, "Failed to select clock source", \
-                "<qt>Could not select %s as clock source.</qt>" % clockname, \
-                QMessageBox.Ok )
+        
+        # only update if the current clock is different
+        if clock != selected:
+            self.clockselect.select(clock)
+            selected = self.clockselect.selected()
+        
+            # show error in case clock was not updated for some reason
+            if selected != clock:
+                clockname = self.clockselect.getEnumLabel( clock )
+                msg = QMessageBox()
+                msg.question( msg, "Failed to select clock source", \
+                    "<qt>Could not select %s as clock source.</qt>" % 
clockname, \
+                    QMessageBox.Ok )
+                return
+                
+            # finally only update GUI
             self.clocksource.setCurrentIndex( selected )
 
     @pyqtSlot(int)
@@ -71,33 +85,46 @@
         # If there's no clock, don't bother trying to set the sample rate
         if (self.no_clock == 1):
             return
-        if self.samplerateselect.canChangeValue():
-            self.samplerateselect.select( sr )
-            if 'onSamplerateChange' in dir(self):
-                self.onSamplerateChange()
-                log.debug("Mixer configuration updated")
-        else:
+        
+        # get current value from device
+        selected = self.samplerateselect.selected()
+
+        # is streaming active?
+        canChange = self.samplerateselect.canChangeValue()
+
+        # only throw message and return if streaming is active and current
+        # samplerate is different from the new setting
+        if not canChange and sr != selected:
             msg = QMessageBox()
             msg.question( msg, "Error", \
                 "<qt>Sample rate change not permitted. Is streaming 
active?</qt>", \
                 QMessageBox.Ok )
             self.samplerate.setEnabled(False)
             return
+        
+        # only update if the actual samplerate is different
+        if sr != selected:
+            self.samplerateselect.select(sr)
+            selected = self.samplerateselect.selected()
+
+            # show error in case samplerate was not updated for some reason
+            if selected != sr:
+                srname = self.samplerateselect.getEnumLabel( sr )
+                log.debug("Failed to select sample rate %s" % srname)
+                msg = QMessageBox()
+                msg.question( msg, "Failed to select sample rate", \
+                    "<qt>Could not select %s as samplerate.</qt>" % srname, \
+                    QMessageBox.Ok )
+                return
 
-        selected = self.samplerateselect.selected()
-        if selected != sr:
-            srname = self.samplerateselect.getEnumLabel( sr )
-            msg = QMessageBox()
-            msg.question( msg, "Failed to select sample rate", \
-                "<qt>Could not select %s as samplerate.</qt>" % srname, \
-                QMessageBox.Ok )
+            # finally only update GUI
             self.samplerate.setCurrentIndex( selected )
 
     @pyqtSlot()
     def on_txtNickname_returnPressed( self ):
         if self.nickname.canChangeValue():
-            asciiData = self.txtNickname.text().toAscii()
-            self.nickname.setText( asciiData.data() )
+            asciiData = str(self.txtNickname.text())
+            self.nickname.setText( asciiData )
         else:
             self.txtNickname.setText( self.nickname.text() )
 
@@ -230,7 +257,6 @@
                 idxclock = -1
             if idxclock >= 0:
                 self.on_clocksource_activated(idxclock)           
-                self.clocksource.setCurrentIndex(self.clockselect.selected())
                 log.debug("Clock set to index %d (%s)" % (idxclock, clock))
             del clockLabel
         # Samplerate
@@ -252,7 +278,6 @@
                 idxsrate = -1
             if idxsrate >= 0:
                 self.on_samplerate_activated(idxsrate)    
-                
self.samplerate.setCurrentIndex(self.samplerateselect.selected())
                 log.debug("Samplerate set to index %d (%s)" % (idxsrate, 
samplerate))
          
 # vim: et
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libffado-2.4.9/support/mixer-qt4/ffado/panelmanager.py 
new/libffado-2.5.0/support/mixer-qt4/ffado/panelmanager.py
--- old/libffado-2.4.9/support/mixer-qt4/ffado/panelmanager.py  2022-02-25 
13:25:10.000000000 +0100
+++ new/libffado-2.5.0/support/mixer-qt4/ffado/panelmanager.py  2025-10-01 
13:59:12.000000000 +0200
@@ -528,8 +528,13 @@
           f.write(s)
         f.close()
 
-    def readSettings(self):
-        readfilename = QFileDialog.getOpenFileName(self, 'Open File', 
os.getenv('HOME'))
+    def readSettings(self, path):
+        if path == None or os.path.exists(path) == False:
+            # use the file dialog to get a file name
+            readfilename = QFileDialog.getOpenFileName(self, 'Open File', 
os.getenv('HOME'))
+        else:
+            readfilename = path
+            
         if isinstance(readfilename, tuple): # newer PyQt5
             readfilename = readfilename[0]
         try:
@@ -608,7 +613,7 @@
               log.debug("Device %s not present; settings ignored" % guid)
 
           del stringDev[:]
-          del readString[idxb:idxe]
+          del readString[idxb:idxe+1]
           nd -= 1
    
 # vim: et
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libffado-2.4.9/support/mixer-qt4/ffado/widgets/crossbarrouter.py 
new/libffado-2.5.0/support/mixer-qt4/ffado/widgets/crossbarrouter.py
--- old/libffado-2.4.9/support/mixer-qt4/ffado/widgets/crossbarrouter.py        
2022-11-27 12:24:03.000000000 +0100
+++ new/libffado-2.5.0/support/mixer-qt4/ffado/widgets/crossbarrouter.py        
2025-03-25 11:29:42.000000000 +0100
@@ -248,11 +248,19 @@
                 for s in routerReadString[idxb+1:idxe]:
                     destination = s.split()[0]
                     if str(self.destinations).find(destination) != -1:
-                        source = s.split()[1]
-                        if sources.find(source) != -1:                        
+                        source = s.split()
+                        if len(source) > 1:
+                            source = source[1]
+                        else:
+                            source = ""
+
+                        if source != "" and sources.find(source) != -1:
                             idx = 
self.switchers[destination].combo.findText(source)
                             
self.switchers[destination].combo.setCurrentIndex(idx)
                             
self.switchers[destination].comboCurrentChanged(source)
+                        else:
+                            
self.switchers[destination].combo.setCurrentIndex(0)
+                            self.switchers[destination].comboCurrentChanged(0)
         return True
 #
 # vim: sw=4 ts=4 et
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libffado-2.4.9/support/mixer-qt4/ffado/widgets/matrixmixer.py 
new/libffado-2.5.0/support/mixer-qt4/ffado/widgets/matrixmixer.py
--- old/libffado-2.4.9/support/mixer-qt4/ffado/widgets/matrixmixer.py   
2022-07-10 08:57:18.000000000 +0200
+++ new/libffado-2.5.0/support/mixer-qt4/ffado/widgets/matrixmixer.py   
2025-07-30 11:21:28.000000000 +0200
@@ -39,15 +39,15 @@
 def toDBvalue(value):
     n = int(value)
     c2p14 = 16384.0
-    if n > 164:
+    if n > 16:
         return round(20.0*math.log10(float(n)/c2p14), 2)
     else:
-        return -40.0
+        return -60.0
 
 def fromDBvalue(value):
     v = float(value)
     c2p14 = 16384.0
-    if (v > -40):
+    if (v > -60.0):
         return int(round(math.pow(10.0, (value/20.0))*c2p14, 0))
     else:
         return 0
@@ -114,6 +114,9 @@
         QAbstractSlider.__init__(self, parent)
         #log.debug("MixerNode.__init__( %i, %i, %i, %i, %s )" % (input, 
output, value, max, str(parent)) )
 
+        self.vol_min = -60.0
+        self.vol_max = 6.0
+
         # Store a direct link back to the underlying matrix object so the mute
         # and invert interfaces can be easily found.  By the time the matrix 
         # has been set into the full widget hierarchy, its parent is unlikely
@@ -139,17 +142,18 @@
         self.mapper.mapped['QString'].connect(self.directValues)
 
         self.spinbox = QDoubleSpinBox(self)
-        self.spinbox.setRange(-40, 12)
+        self.spinbox.setRange(self.vol_min, self.vol_max)
+        self.spinbox.setDecimals(1)
         self.spinbox.setSuffix(" dB")
         if value != 0:
             self.spinbox.setValue(toDBvalue(value))            
 
-        self.spinbox.valueChanged.connect(self.directValues)
+        self.spinbox.editingFinished.connect(self.spinBoxSetsValue)
         action = QWidgetAction(self)
         action.setDefaultWidget(self.spinbox)
         self.addAction(action)
 
-        for text in ["3 dB", "0 dB", "-3 dB", "-20 dB", "-inf dB"]:
+        for text in ["3 dB", "0 dB", "-3 dB", "-10 dB", "-15 dB", "-20 dB", 
"-25 dB", "-30 dB", "-35 dB", "-40 dB", "-45 dB", "-50 dB", "-55 dB", "-inf 
dB"]:
             action = QAction(text, self)
             action.triggered.connect(self.mapper.map)
             self.mapper.setMapping(action, text)
@@ -158,7 +162,7 @@
         # Only show the mute menu item if a value has been supplied
         self.mute_action = None
         if (muted != None):
-            action = QAction(text, self)
+            action = QAction(None, self)
             action.setSeparator(True)
             self.addAction(action)
             self.mute_action = QAction("Mute", self)
@@ -182,6 +186,11 @@
             self.mapper.setMapping(self.inv_action, "Invert")
             self.addAction(self.inv_action)
 
+    def spinBoxSetsValue(self):
+        n = fromDBvalue(self.spinbox.value())
+        #log.debug("  linear value: %g" % n)
+        self.setValue(n)
+
     def directValues(self,text):
         #log.debug("MixerNode.directValues( '%s' )" % text)
         if text == "Mute":
@@ -201,40 +210,41 @@
     def mousePressEvent(self, ev):
         if ev.buttons() & Qt.LeftButton:
             self.pos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
-            self.tmpvalue = self.value()
+            self.dBval = toDBvalue(self.value())
             ev.accept()
             #log.debug("MixerNode.mousePressEvent() %s" % str(self.pos))
 
     def mouseMoveEvent(self, ev):
-        if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
+        if hasattr(self, "dBval") and self.pos is not QtCore.QPointF(0, 0):
             newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
             change = newpos.y() - self.pos.y()
-            #log.debug("MixerNode.mouseReleaseEvent() change %s" % 
(str(change)))
+            #log.debug("MixerNode.mouseMoveEvent() change %s" % (str(change)))
             self.setValue(
-                int(self.tmpvalue - math.copysign(pow(abs(change), 2), change))
+                int(fromDBvalue(self.dBval - change / 10.0))
             )
             ev.accept()
 
     def mouseReleaseEvent(self, ev):
-        if hasattr(self, "tmpvalue") and self.pos is not QtCore.QPointF(0, 0):
+        if hasattr(self, "dBval") and self.pos is not QtCore.QPointF(0, 0):
             newpos = ev.posF() if ffado_pyqt_version == 4 else ev.localPos()
             change = newpos.y() - self.pos.y()
             #log.debug("MixerNode.mouseReleaseEvent() change %s" % 
(str(change)))
             self.setValue(
-                int(self.tmpvalue - math.copysign(pow(abs(change), 2), change))
+                int(fromDBvalue(self.dBval - change / 10.0))
             )
             self.pos = QtCore.QPointF(0, 0)
-            del self.tmpvalue
+            del self.dBval
             ev.accept()
 
     # Wheel event is mainly for scrolling inside the mixer window
     #   Additionnaly press Control key for wheel controling the values
     def wheelEvent (self, ev):
         if (ev.modifiers() & Qt.ControlModifier):
-            tmpvalue = self.value()
-            change = ev.delta()/8
+            self.dBvalW = toDBvalue(self.value())
+            change = ev.angleDelta().y() / 120
+            #log.debug("MixerNode.wheelEvent() change %s" % (str(change)))
             self.setValue(
-                int(tmpvalue + math.copysign(pow(abs(change), 2), change))
+                int(fromDBvalue(self.dBvalW + change))
             )
             ev.accept()
         else:
@@ -259,7 +269,7 @@
         if v != 0:
             lv = toDBvalue(v)
             #log.debug("new value is %g dB" % lv)
-        text = "%.2g dB" % lv
+        text = "%.1f dB" % lv
         if v == 0:
             symb_inf = u"\u221E"
             text = "-" + symb_inf + " dB"
@@ -292,7 +302,7 @@
             self.setMinimumSize(10, 10)
         else:
             fontmetrics = self.fontMetrics()
-            self.setMinimumSize(fontmetrics.boundingRect("-0.0 dB").size()*1.1)
+            self.setMinimumSize(fontmetrics.boundingRect("-44.4 
dB").size()*1.2)
         self.update()
 
 class MixerChannel(QWidget):
@@ -696,10 +706,12 @@
     def __init__(self, In, Out, value, parent):
         QSlider.__init__(self, QtCore.Qt.Vertical, parent)
 
+        self.vol_min = -60.0
+        self.vol_max = 6.0
         self.setTickPosition(QSlider.TicksBothSides)
-        v_min = 10.0*toDBvalue(0)
-        v_max = 10.0*toDBvalue(65536)
-        self.setTickInterval(int((v_max-v_min)/10))
+        v_min = 10.0*self.vol_min
+        v_max = 10.0*self.vol_max
+        self.setTickInterval(int((self.vol_max-self.vol_min)/10))
         self.setMinimum(int(v_min))
         self.setMaximum(int(v_max))
         self.setSingleStep(1)
@@ -726,25 +738,56 @@
 
 
 class VolumeSliderValueInfo(QLineEdit):
+    valueEdited = pyqtSignal(tuple)
     def __init__(self, In, Out, value, parent):
         QLineEdit.__init__(self, parent)
 
-        self.setReadOnly(True)
+        self.vol_min = -60.0
+        self.vol_max = 6.0
+
+        self.setReadOnly(False)
+        self.editingFinished.connect(self.editDone)
         self.setAlignment(Qt.AlignCenter)
         self.setAutoFillBackground(True)
         self.setFrame(False)
 
-        self.sliderSetMinimalDim()
+        self.labelSetMinimalDim()
 
         self.bgcolors = BckgrdColorForNumber()
 
-        self.sliderSetValue(value)
+        self.In = In
+        self.Out = Out
+
+        self.labelSetValue(value)
+
+    def editDone(self):
+        text = str(self.text()).split(" ")[0].replace(",",".")
+        if text == "-\u221E":
+            value = 0
+        else:
+            text = str(self.text()).split(" ")[0].replace(",",".")
+            if self.isFloat(text):
+                value = fromDBvalue(min(float(text), self.vol_max))
+            else:
+                value = 0
+        #log.debug("VolumeSliderValueInfo  linear value: %g" % value)
+        self.valueEdited.emit((self.In, self.Out, value))
+        self.clearFocus()
+        self.labelSetValue(value)
+        self.update()
+
+    def isFloat(self, v):
+        try:
+            f=float(v)
+        except ValueError:
+            return False
+        return True
 
-    def sliderSetMinimalDim(self):
+    def labelSetMinimalDim(self):
         fontmetrics = self.fontMetrics()
-        self.setMinimumSize(fontmetrics.boundingRect("-00.0 dB").size()*1.1)
+        self.setMinimumSize(fontmetrics.boundingRect("-44.4 dB").size()*1.2)
         
-    def sliderSetValue(self, value):
+    def labelSetValue(self, value):
         color = self.bgcolors.getColor(value)
         palette = self.palette()
         palette.setColor(QPalette.Active, QPalette.Base, color)
@@ -752,7 +795,7 @@
         self.setPalette(palette)
 
         v = round(toDBvalue(value),1)
-        if (v > -40):
+        if (v > self.vol_min):
             text = "%.1f dB" % v
         else:
             symb_inf = u"\u221E"
@@ -879,6 +922,7 @@
                 svl = VolumeSliderValueInfo(j, i, self.getVolumeValue(j,i), 
h_v_layout_wid)
                 h_v_layout.addWidget(svl)
                 self.out[i].svl.append(svl)
+                self.volumeLabelConnect(svl)
 
                 # Balance fader
                 if self.out[i].is_stereo:
@@ -896,6 +940,12 @@
     def volumeDisconnect(self, volume):
         volume.sliderChanged.disconnect(self.valueChangedVolume)
 
+    def volumeLabelConnect(self, svl):
+        svl.valueEdited.connect(self.valueChangedVolume)
+
+    def volumeLabelDisconnect(self, svl):
+        svl.valueEdited.disconnect(self.valueChangedVolume)
+
     def balanceConnect(self, balance):
         balance.sliderChanged.connect(self.valueChangedBalance)
 
@@ -966,21 +1016,25 @@
             if (self.out[i].is_stereo):
                 v = self.getVolumeValue(n_0, i)
                 self.volumeDisconnect(self.out[i].volume[n_0])
+                self.volumeLabelDisconnect(self.out[i].svl[n_0])
                 self.balanceDisconnect(self.out[i].balance[n_0])
                 self.out[i].volume[n_0].sliderSetValue(v)
-                self.out[i].svl[n_0].sliderSetValue(v)
+                self.out[i].svl[n_0].labelSetValue(v)
                 b = self.getBalanceValue(n_0, i)       
                 # log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b))
                 self.out[i].balance[n_0].sliderSetValue(b)
                 self.volumeConnect(self.out[i].volume[n_0])
+                self.volumeLabelConnect(self.out[i].svl[n_0])
                 self.balanceConnect(self.out[i].balance[n_0])
             else:
                 v = n_2
                 # log.debug("update Value (%d %d %d)" % (n_0, i, v))
                 self.volumeDisconnect(self.out[i].volume[n_0])
+                self.volumeLabelDisconnect(self.out[i].svl[n_0])
                 self.out[i].volume[n_0].sliderSetValue(v)
-                self.out[i].svl[n_0].sliderSetValue(v)
+                self.out[i].svl[n_0].labelSetValue(v)
                 self.volumeConnect(self.out[i].volume[n_0])
+                self.volumeLabelConnect(self.out[i].svl[n_0])
         
     def valueChangedVolume(self, n):
         #log.debug("VolumeSlider.valueChanged( %s )" % str(n))
@@ -999,7 +1053,8 @@
             self.setValue(n[0], n1, v)
             n_t = (n[0], n1, v)
             self.valueChanged.emit(n_t)
-        self.out[n[1]].svl[n[0]].sliderSetValue(v)
+        self.out[n[1]].svl[n[0]].labelSetValue(v)
+        self.out[n[1]].volume[n[0]].sliderSetValue(v)
 
     def valueChangedBalance(self, n):
         #log.debug("BalanceSlider.valueChanged( %s )" % str(n))
@@ -1078,16 +1133,18 @@
                 v = self.getVolumeValue(n_in, i)
                 if (self.out[i].is_stereo):
                     self.volumeDisconnect(self.out[i].volume[n_in])
+                    self.volumeLabelDisconnect(self.out[i].svl[n_in])
                     self.out[i].volume[n_in].sliderSetValue(v)
-                    self.out[i].svl[n_in].sliderSetValue(v)
+                    self.out[i].svl[n_in].labelSetValue(v)
                     b = self.getBalanceValue(n_in, i)       
                     # log.debug("update Value (%d %d %d %f)" % (n_0, i, v, b))
                     self.out[i].balance[n_in].sliderSetValue(b)
                     self.volumeConnect(self.out[i].volume[n_in])
+                    self.volumeLabelConnect(self.out[i].svl[n_in])
                 else:
                     # log.debug("update Value (%d %d %d)" % (n_0, i, v))
                     self.out[i].volume[n_in].sliderSetValue(v)
-                    self.out[i].svl[n_in].sliderSetValue(v)
+                    self.out[i].svl[n_in].labelSetValue(v)
 
     def saveSettings(self, indent):
         if ffado.config.bypassdbus:
@@ -1238,7 +1295,7 @@
         font_switch.setToolTip("Labels font size")
         font = font_switch.font()
         for i in range(10):
-            font_switch.addItem(" %d " % (font.pointSize()+4-i))
+            font_switch.addItem(" %d " % (6 + i))
         font_switch.setCurrentIndex(font_switch.findText(" %d " % 
font.pointSize()))
         mxv_set.addWidget(font_switch)
         mxv_set.addSeparator()
@@ -1351,6 +1408,8 @@
 
     # Font size for channel names
     def changeFontSize(self, size):
+        size = size + 6
+
         font = self.mxv_set.font()
         font.setPointSize(int(size))
         self.mxv_set.setFont(font)
@@ -1369,7 +1428,7 @@
 
         for i in range(self.perOut.nbOut):
             for j in range(self.perOut.nbIn):
-                self.perOut.out[i].svl[j].sliderSetMinimalDim()
+                self.perOut.out[i].svl[j].labelSetMinimalDim()
 
     # Allows long name for Mixer/Out and /In to be hidden 
     def shortChannelNames(self):
@@ -1529,7 +1588,7 @@
                     log.debug("No <channels> tag found")
                     return False
                 for s in readMixerString[idx+5:idx+5+n]:
-                    i = (int(s.split()[0]) - 1)/2
+                    i = int((int(s.split()[0]) - 1)/2)
                     self.stereo_switch[i].setChecked(True);
                     self.switchStereoChannel(i, True)
         return True

Reply via email to