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

Revision 2472, 20.3 kB (checked in by philippe, 9 years ago)

ffado-mixer/DICE EAP: file saving of settings. File is xml so as to be human readable. File version included (as 0.1). Guid included. Partial commit: only global mixer and router settings are saved.

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