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

Revision 2473, 20.4 kB (checked in by philippe, 9 years ago)

ffado-mixer/DICE EAP: file saving of settings. Complementary commit: Add saving of mixer section

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