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>© 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>© 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
