root/trunk/libffado/support/mixer-qt4/ffado/panelmanager.py

Revision 2791, 23.0 kB (checked in by jwoithe, 4 years ago)

ffado-mixer: disable the optional registration prompt.

While usage statistics were useful in the past when discussing Linux support
with vendors, in 2020 this is no longer the case. Vendors are generally no
longer shipping Firewire-based products so these statistics are not of
practical value anymore. Since they are no longer used there is little
point to collect them, so disable the registration dialog box.

While this change has been pondered for a while, it has been brought to a
head by the changes to the website infrastructure in 2020. To continue to
support the device registration, the website services used by the
registration code would need to be supported. Given the limited utility of
the data produced, the time required to do this does not seem worthwhile.

  • Property svn:mergeinfo set to
Line 
1 #
2 # Copyright (C) 2005-2008 by Pieter Palmers
3 #               2007-2008 by Arnold Krille
4 #               2013 by Philippe Carriere
5 #
6 # This file is part of FFADO
7 # FFADO = Free Firewire (pro-)audio drivers for linux
8 #
9 # FFADO is based upon FreeBoB.
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 from ffado.config import * #FFADO_VERSION, FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH
26
27 # from PyQt4.QtGui import QFrame, QWidget, QTabWidget, QVBoxLayout, QMainWindow, QIcon, QAction, qApp, QStyleOptionTabWidgetFrame, QFileDialog
28 # from PyQt4.QtCore import QTimer, pyqtSignal
29 from ffado.import_pyqt import *
30
31 from ffado.dbus_util import *
32 from ffado.registration import *
33
34 from ffado.configuration import *
35
36 from ffado.mixer.globalmixer import GlobalMixer
37 from ffado.mixer.dummy import Dummy
38
39 import sys
40 import time
41 import importlib
42
43 import logging
44 log = logging.getLogger('panelmanager')
45
46 use_generic = False
47 try:
48     from mixer_generic import *
49     log.info("The generic mixer is found, seems to be a developer using ffadomixer...")
50 except ImportError:
51     pass
52 else:
53     use_generic = True
54
55 # pseudo-guid
56 GUID_GENERIC_MIXER = 0
57
58 FILE_VERSION = '0.1'
59
60 class HLine( QFrame ):
61     def __init__( self, parent ):
62         QFrame.__init__( self, parent )
63         self.setFrameShape( QFrame.HLine )
64         self.setLineWidth( 2 )
65         self.setMinimumHeight( 10 )
66
67 class PanelManagerStatus(QWidget):
68     def __init__(self, parent):
69         QWidget.__init__(self,parent)
70         uicLoad("ffado/panelmanagerstatus", self)
71
72 class OwnTabWidget(QTabWidget):
73     def __init__(self,parent):
74         QTabWidget.__init__(self,parent)
75
76     def tabInserted(self,index):
77         self.checkTabBar()
78
79     def tabRemoved(self,index):
80         self.checkTabBar()
81
82     def checkTabBar(self):
83         if self.count()<2:
84             self.tabBar().hide()
85         else:
86             self.tabBar().show()
87
88 class PanelManager(QWidget):
89     connectionLost = pyqtSignal(name='connectionLost')
90     def __init__(self, parent, devmgr=None):
91         QWidget.__init__(self, parent)
92         self.setObjectName("PanelManager")
93         self.parent = parent
94
95         # maps a device GUID to a QT panel
96         self.panels = {}
97
98         # a layout for ourselves
99         self.layout = QVBoxLayout(self)
100
101         # the tabs
102         self.tabs = OwnTabWidget(self)
103         self.tabs.hide()
104         self.layout.addWidget(self.tabs)
105
106         # a dialog that is shown during update
107         self.status = PanelManagerStatus(self)
108         self.layout.addWidget(self.status)
109         self.status.show()
110
111         self.devices = DeviceList( SYSTEM_CONFIG_FILE )
112         self.devices.updateFromFile( USER_CONFIG_FILE )
113
114         if devmgr is not None:
115             self.setManager(devmgr)
116
117     def __del__(self):
118         print("PanelManager.__del__()")
119         self.polltimer.stop()
120
121     def setManager(self,devmgr):
122         self.devmgr = devmgr
123         self.devmgr.registerPreUpdateCallback(self.devlistPreUpdate)
124         self.devmgr.registerPostUpdateCallback(self.devlistPostUpdate)
125         self.devmgr.registerUpdateCallback(self.devlistUpdate)
126         self.devmgr.registerDestroyedCallback(self.devmgrDestroyed)
127         # create a timer to poll the panels
128         self.polltimer = QTimer()
129         self.polltimer.timeout.connect(self.pollPanels)
130         self.polltimer.start( POLL_SLEEP_TIME_MSEC )
131
132         # create a timer to initialize the panel after the main form is shown
133         # since initialization can take a while
134         QTimer.singleShot( POLL_SLEEP_TIME_MSEC, self.updatePanels )
135
136         # live check timer
137         self.alivetimer = QTimer()
138         self.alivetimer.timeout.connect(self.commCheck)
139         self.alivetimer.start( 2000 )
140
141     def count(self):
142         return self.tabs.count()
143
144     def pollPanels(self):
145         #log.debug("PanelManager::pollPanels()")
146         # only when not modifying the tabs
147         try:
148             if self.tabs.isEnabled():
149                 for guid in self.panels.keys():
150                     w = self.panels[guid]
151                     for child in w.children():
152                         #log.debug("poll child %s,%s" % (guid,child))
153                         if 'polledUpdate' in dir(child):
154                             child.polledUpdate()
155         except:
156             log.error("error in pollPanels")
157             self.commCheck()
158
159     def devlistPreUpdate(self):
160         log.debug("devlistPreUpdate")
161         self.tabs.setEnabled(False)
162         self.tabs.hide()
163         self.status.lblMessage.setText("Bus reconfiguration in progress, please wait...")
164         self.status.show()
165         #self.statusBar().showMessage("bus reconfiguration in progress...", 5000)
166
167     def devlistPostUpdate(self):
168         log.debug("devlistPostUpdate")
169         # this can fail if multiple busresets happen in fast succession
170         ntries = 10
171         while ntries > 0:
172             try:
173                 self.updatePanels()
174                 return
175             except:
176                 log.debug("devlistPostUpdate failed (%d)" % ntries)
177                 for guid in self.panels.keys():
178                     w = self.panels[guid]
179                     del self.panels[guid] # remove from the list
180                     idx = self.tabs.indexOf(w)
181                     self.tabs.removeTab(idx)
182                     del w # GC might also take care of that
183
184                 ntries = ntries - 1
185                 time.sleep(2) # sleep a few seconds
186
187         log.debug("devlistPostUpdate failed completely")
188         self.tabs.setEnabled(False)
189         self.tabs.hide()
190         self.status.lblMessage.setText("Error while reconfiguring. Please restart ffadomixer.")
191         self.status.show()
192
193
194     def devlistUpdate(self):
195         log.debug("devlistUpdate")
196
197     def devmgrDestroyed(self):
198         log.debug("devmgrDestroyed")
199         self.alivetimer.stop()
200         self.tabs.setEnabled(False)
201         self.tabs.hide()
202         self.status.lblMessage.setText("DBUS server was shut down, please restart it and restart ffadomixer...")
203         self.status.show()
204
205     def commCheck(self):
206         try:
207             nbDevices = self.devmgr.getNbDevices()
208         except:
209             log.error("The communication with ffado-dbus-server was lost.")
210             self.tabs.setEnabled(False)
211             self.polltimer.stop()
212             self.alivetimer.stop()
213             keys = self.panels.keys()
214             for panel in keys:
215                 w = self.panels[panel]
216                 del self.panels[panel]
217                 w.deleteLater()
218             self.connectionLost.emit()
219
220     def removePanel(self, guid):
221         print( "Removing widget for device" + guid )
222         w = self.panels[guid]
223         del self.panels[guid] # remove from the list
224         idx = self.tabs.indexOf(w)
225         self.tabs.removeTab(idx)
226         w.deleteLater()
227         self.parent.editmenu.removeAction(self.parent.devices[guid])
228         self.parent.devices.pop(guid, None)
229
230     def addPanel(self, idx):
231         path = self.devmgr.getDeviceName(idx)
232         log.debug("Adding device %d: %s" % (idx, path))
233
234         if ffado.config.bypassdbus:
235             cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, path)
236         else:
237             cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
238         vendorId = cfgrom.getVendorId()
239         modelId = cfgrom.getModelId()
240         unitVersion = cfgrom.getUnitVersion()
241         guid = cfgrom.getGUID()
242         vendorName = cfgrom.getVendorName()
243         modelName = cfgrom.getModelName()
244         log.debug(" Found (%s, %X, %X) %s %s" % (str(guid), vendorId, modelId, vendorName, modelName))
245
246         # check whether this has already been registered at ffado.org
247         if not ffado.config.bypassdbus:
248             # As of June 2020, don't offer the registration option as the
249             # statistics are no longer useful.  Coincidently, a move to
250             # different website infrastructure June 2020 would necessitate
251             # some work to reimplement the interfaces used by the
252             # registration process, which doesn't seem worthwhile.
253             #
254             # reg = ffado_registration(FFADO_VERSION, int(guid, 16),
255             #                              vendorId, modelId,
256             #                              vendorName, modelName)
257             # reg.check_for_registration()
258
259             # The MOTU devices use unitVersion to differentiate models.  For
260             # the moment though we don't need to know precisely which model
261             # we're using beyond it being a pre-mark3 (modelId=0) or mark3
262             # (modelId=1) device.
263             if vendorId == 0x1f2:
264                 # All MOTU devices with a unit version of 0x15 or greater are
265                 # mark3 devices
266                 if (unitVersion >= 0x15):
267                     modelId = 0x00000001
268                 else:
269                     modelId = 0x00000000
270
271             # The RME devices use unitVersion to differentiate models.
272             # Therefore in the configuration file we use the config file's
273             # modelid field to store the unit version.  As a result we must
274             # override the modelId with the unit version here so the correct
275             # configuration file entry (and hense mixer widget) is identified.
276             if vendorId == 0xa35:
277                 modelId = unitVersion;
278
279         dev = self.devices.getDeviceById( vendorId, modelId )
280
281         w = QWidget( )
282         l = QVBoxLayout( w )
283
284         # create a control object
285         hw = ControlInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
286         clockselect = ClockSelectInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path )
287         samplerateselect = SamplerateSelectInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path )
288         streamingstatus = StreamingStatusInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path )
289         nickname = TextInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path+"/Generic/Nickname" )
290
291         #
292         # Generic elements for all mixers follow here:
293         #
294         globalmixer = GlobalMixer( w )
295         globalmixer.configrom = cfgrom
296         globalmixer.clockselect = clockselect
297         globalmixer.samplerateselect = samplerateselect
298         globalmixer.streamingstatus = streamingstatus
299         globalmixer.nickname = nickname
300         globalmixer.hw = hw
301         globalmixer.initValues()
302         l.addWidget( globalmixer, 1 )
303
304         #
305         # Line to separate
306         #
307         l.addWidget( HLine( w ) )
308
309         #
310         # Specific (or dummy) mixer widgets get loaded in the following
311         #
312         found = False
313         if 'mixer' in dev and dev['mixer'] != None:
314             mixerapp = dev['mixer']
315             try:
316                 mixer_module = importlib.import_module("ffado.mixer.%s" % mixerapp.lower())
317                 mixerwidget = getattr(mixer_module, mixerapp)(w)
318                 found = True
319             except ImportError:
320                 log.debug("bypassdbus set, %s module not available: ignored" % mixerapp.lower())
321
322         if not found:
323             mixerwidget = Dummy( w )
324             mixerapp = modelName+" (Dummy)"
325
326         #
327         # The same for all mixers
328         #
329         l.addWidget( mixerwidget, 10 )
330         mixerwidget.configrom = cfgrom
331         mixerwidget.clockselect = clockselect
332         mixerwidget.samplerateselect = samplerateselect
333         mixerwidget.streamingstatus = streamingstatus
334         mixerwidget.nickname = nickname
335         mixerwidget.hw = hw
336         if 'buildMixer' in dir(mixerwidget):
337             mixerwidget.buildMixer()
338         if 'initValues' in dir(mixerwidget):
339             mixerwidget.initValues()
340         if 'getDisplayTitle' in dir(mixerwidget):
341             title = mixerwidget.getDisplayTitle()
342         else:
343             title = mixerapp
344
345         mixer_icon = UIDIR + '/ffado/mixer/' + vendorName.replace(" ", "_").lower() + '.png'
346         if os.path.exists(mixer_icon) :
347             globalmixer.lblName.setPixmap(QPixmap(mixer_icon))
348             globalmixer.lblName.setToolTip(title)
349             globalmixer.lblName.show()
350         else :
351             globalmixer.setName(title)
352
353         self.tabs.addTab( w, title )
354         self.panels[guid] = w
355
356         if 'onSamplerateChange' in dir(mixerwidget):
357           log.debug("Updating Mixer on samplerate change required")
358           globalmixer.onSamplerateChange = mixerwidget.onSamplerateChange
359
360         w.gmixSaveSetgs = globalmixer.saveSettings
361         w.gmixReadSetgs = globalmixer.readSettings
362         if 'saveSettings' in dir(mixerwidget):
363           w.smixSaveSetgs = mixerwidget.saveSettings
364           self.parent.saveaction.setEnabled(True)
365
366         if 'readSettings' in dir(mixerwidget):
367           w.smixReadSetgs = mixerwidget.readSettings
368           self.parent.openaction.setEnabled(True)
369
370         self.parent.devices[guid] = QAction(QIcon.fromTheme("audio-card"), str(title), self.parent)
371         # Ensure a standard type is passed to setDate() so a UserType is not
372         # adopted by the QVariant which underpins the QAction data.  "guid"
373         # is a dbus.String object; it will be stored as a UserType if passed
374         # directly, which makes it difficult to extract in setTabVisible().
375         self.parent.devices[guid].setData(str(guid))
376         self.parent.editmenu.addAction(self.parent.devices[guid])
377         self.parent.devices[guid].triggered.connect(self.setTabVisible)
378    
379     def setTabVisible(self) :
380         action = self.sender()
381         # Extract the action data and store as a dbus.String type so
382         # it is usable as a key into self.panels[].
383         panel_key = dbus.String(action.data().toString() if ffado_pyqt_version == 4 else action.data())
384         self.tabs.setCurrentIndex(self.tabs.indexOf(self.panels[panel_key]))
385
386     def displayPanels(self):
387         # if there is no panel, add the no-device message
388         # else make sure it is not present
389         if self.count() == 0:
390             self.tabs.hide()
391             self.tabs.setEnabled(False)
392             self.status.lblMessage.setText("No supported device found.")
393             self.status.show()
394             #self.statusBar().showMessage("No supported device found.", 5000)
395         else:
396             # Hide the status widget before showing the panel tab to prevent
397             # the panel manager's vertical size including that of the status
398             # widget.  For some reason, hiding the status after showing the
399             # tabs does not cause a recalculation of the panel manager's size,
400             # and the window ends up being larger than it needs to be.
401             self.status.hide()
402             self.tabs.show()
403             self.tabs.setEnabled(True)
404             #self.statusBar().showMessage("Configured the mixer for %i devices." % self.tabs.count())
405             if use_generic:
406                 #
407                 # Show the generic (development) mixer if it is available
408                 #
409                 w = GenericMixer( devmgr.bus, FFADO_DBUS_SERVER, mw )
410                 self.tabs.addTab( w, "Generic Mixer" )
411                 self.panels[GUID_GENERIC_MIXER] = w
412    
413     def updatePanels(self):
414         log.debug("PanelManager::updatePanels()")
415         nbDevices = self.devmgr.getNbDevices()
416         #self.statusBar().showMessage("Reconfiguring the mixer panels...")
417
418         # list of panels present
419         guids_with_tabs = self.panels.keys()
420
421         # build list of guids on the bus now
422         guids_present = []
423         guid_indexes = {}
424         for idx in range(nbDevices):
425             path = self.devmgr.getDeviceName(idx)
426             if ffado.config.bypassdbus:
427                 cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, path)
428             else:
429                 cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
430             guid = cfgrom.getGUID()
431             guids_present.append(guid)
432             guid_indexes[guid] = idx
433
434         # figure out what to remove
435         # the special panel (generic)
436         # that has (pseudo-)GUID 0
437         # is also automatically removed
438         to_remove = []
439         for guid in guids_with_tabs:
440             if not guid in guids_present:
441                 to_remove.append(guid)
442                 log.debug("going to remove %s" % str(guid))
443             else:
444                 log.debug("going to keep %s" % str(guid))
445
446         # figure out what to add
447         to_add = []
448         for guid in guids_present:
449             if not guid in guids_with_tabs:
450                 to_add.append(guid)
451                 log.debug("going to add %s" % str(guid))
452
453         # update the widget
454         for guid in to_remove:
455             self.removePanel(guid)
456
457         for guid in to_add:
458             # retrieve the device manager index
459             idx = guid_indexes[guid]
460             self.addPanel(idx)
461
462         self.displayPanels()
463
464     def refreshPanels(self):
465         log.debug("PanelManager::refreshPanels()")
466         nbDevices = self.devmgr.getNbDevices()
467         #self.statusBar().showMessage("Reconfiguring the mixer panels...")
468
469         # list of panels present
470         guids_with_tabs = self.panels.keys()
471
472         # build list of guids on the bus now
473         guid_indexes = {}
474         for idx in range(nbDevices):
475             path = self.devmgr.getDeviceName(idx)
476             cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
477             guid = cfgrom.getGUID()
478             guid_indexes[guid] = idx
479
480         # remove/create the widget
481         for guid in guids_with_tabs:
482             self.removePanel(guid)
483             idx = guid_indexes[guid]
484             self.addPanel(idx)
485
486         self.displayPanels()
487
488     def saveSettings(self):
489         saveString = []
490         saveString.append('<?xml version="1.0" encoding="UTF-8"?>\n')
491         saveString.append('<fileversion>\n')
492         saveString.append('  <major>\n')
493         saveString.append('    ' + str(FILE_VERSION).split('.')[0] + '\n')
494         saveString.append('  </major>\n')
495         saveString.append('  <minor>\n')
496         saveString.append('    ' + str(FILE_VERSION).split('.')[1] + '\n')
497         saveString.append('  </minor>\n')
498         saveString.append('</fileversion>\n')
499         saveString.append('<ffadoversion>\n')
500         saveString.append('  <major>\n')
501         saveString.append('    ' + str(str(FFADO_VERSION).split('-')[0]).split('.')[0] + '\n')
502         saveString.append('  </major>\n')
503         saveString.append('  <minor>\n')
504         saveString.append('    ' + str(str(FFADO_VERSION).split('-')[0]).split('.')[1] + '\n')
505         saveString.append('  </minor>\n')
506         saveString.append('</ffadoversion>\n')
507         for guid in self.panels.keys():
508           saveString.append('<device>\n')
509           saveString.append('  <guid>\n')
510           saveString.append('    ' + str(guid) + '\n')
511           saveString.append('  </guid>\n')       
512           w = self.panels[guid]
513           indent = "  "
514           saveString.extend(w.gmixSaveSetgs(indent))
515           if 'smixSaveSetgs' in dir(w):
516               saveString.extend(w.smixSaveSetgs(indent))
517           saveString.append('</device>\n')
518         # file saving
519         savefilename = QFileDialog.getSaveFileName(self, 'Save File', os.getenv('HOME'))
520         if isinstance(savefilename, tuple): # newer PyQt5
521             savefilename = savefilename[0]
522         try:
523           f = open(savefilename, 'w')
524         except IOError:
525           print( "Unable to open save file" )
526           return
527         for s in saveString:
528           f.write(s)
529         f.close()
530
531     def readSettings(self):
532         readfilename = QFileDialog.getOpenFileName(self, 'Open File', os.getenv('HOME'))
533         if isinstance(readfilename, tuple): # newer PyQt5
534             readfilename = readfilename[0]
535         try:
536           f = open(readfilename, 'r')
537         except IOError:
538           print( "Unable to open file" )
539           return
540         log.debug("Opening file %s" % readfilename)
541         # discard useless whitespace characters
542         readString = []
543         for line in f:
544           readString.append(" ".join(str(line).split()))
545         f.close()
546         # Check it is a compatible "FFADO" file
547         # It must start with the <?xml ... tag as the first string
548         if readString[0].find("<?xml") == -1:
549             print( "Not an xml data file" )
550             return
551         # Then there must be a file version tag somewhere in the file
552         try:
553             idx = readString.index('<fileversion>')
554         except Exception:
555             print( "Data file should contain the version tag" )
556             return
557         if readString[idx+1].find("<major>") == -1:
558             print( "Incompatible versioning of the file" )
559         if readString[idx+3].find("</major>") == -1:
560             print( "Not a valid xml file" )
561         if readString[idx+4].find("<minor>") == -1:
562             print( "Incompatible versioning of the file" )
563         if readString[idx+6].find("</minor>") == -1:
564             print( "Not a valid xml file" )
565         version_major = readString[idx+2]
566         version =  version_major + '.' + readString[idx+5]
567         log.debug("File version: %s" % version)
568         # File version newer than present
569         if int(version_major) > int(str(FILE_VERSION).split('.')[0]):
570             print( "File version is too recent: you should upgrade your FFADO installation" )
571             return
572         # FIXME At a time it will be necessary to detect if an older major version is detected
573         #
574         # It looks like useless to check for the FFADO version
575         # Add something here if you would like so
576         #
577         # Now search for devices
578         nd = readString.count('<device>');
579         n  = readString.count('</device>');
580         if n != nd:
581             print( "Not a regular xml file: opening device tag must match closing ones" )
582             return
583         while nd > 0:
584           idxb = readString.index('<device>')
585           idxe = readString.index('</device>')
586           if idxe < idxb+1:
587             print( "Not a regular xml file: data must be enclosed between a <device> and </device> tag" )
588             return
589           stringDev = []
590           for s in readString[idxb:idxe]:
591             stringDev.append(s)
592           # determine the device guid
593           try:
594               idx = stringDev.index('<guid>')
595           except Exception:
596               print( "Device guid not found" )
597               return
598           guid = stringDev[idx+1]
599           log.debug("Device %s found" % guid)
600
601           if guid in self.panels:
602               w = self.panels[guid]
603               w.gmixReadSetgs(stringDev)
604               if 'smixReadSetgs' in dir(w):
605                 w.smixReadSetgs(stringDev)
606               log.debug("Settings changed for device %s" % guid)
607           else:
608               log.debug("Device %s not present; settings ignored" % guid)
609
610           del stringDev[:]
611           del readString[idxb:idxe]
612           nd -= 1
613    
614 # vim: et
Note: See TracBrowser for help on using the browser.