root/trunk/libffado/support/mixer-qt4/ffado_panelmanager.py

Revision 1435, 13.2 kB (checked in by arnonym, 12 years ago)

forward port the mixer-changes from 2.0-branch r1361:HEAD

Line 
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2005-2008 by Pieter Palmers
4 #               2007-2008 by Arnold Krille
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 ffadomixer_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
28 from PyQt4.QtCore import QTimer
29
30 from ffado_panelmanagerstatusui import Ui_PanelManagerStatusUI
31
32 from ffado_dbus_util import *
33 from ffado_registration import *
34
35 from ffado_configuration import DeviceList
36
37 from mixer_phase88 import *
38 from mixer_phase24 import *
39 from mixer_saffire import SaffireMixer
40 from mixer_saffirepro import SaffireProMixer
41 from mixer_audiofire import AudioFireMixer
42 from mixer_bcoaudio5 import *
43 from mixer_edirolfa66 import *
44 from mixer_edirolfa101 import *
45 from mixer_mackie_onyxmixer import *
46 from mixer_quatafire import *
47 from mixer_motu import *
48 from mixer_dummy import *
49 from mixer_global import GlobalMixer
50
51 import time
52
53 import logging
54 log = logging.getLogger('panelmanager')
55
56 use_generic = False
57 try:
58     from mixer_generic import *
59     log.info("The generic mixer is found, seems to be a developer using ffadomixer...")
60 except ImportError:
61     pass
62 else:
63     use_generic = True
64
65 # pseudo-guid
66 GUID_GENERIC_MIXER = 0
67
68
69 class HLine( QFrame ):
70     def __init__( self, parent ):
71         QFrame.__init__( self, parent )
72         self.setFrameShape( QFrame.HLine )
73         self.setLineWidth( 2 )
74         self.setMinimumHeight( 10 )
75
76 class PanelManagerStatus(QWidget, Ui_PanelManagerStatusUI):
77     def __init__(self, parent):
78         QWidget.__init__(self,parent)
79         self.setupUi(self)
80
81 class OwnTabWidget(QTabWidget):
82     def __init__(self,parent):
83         QTabWidget.__init__(self,parent)
84
85     def tabInserted(self,index):
86         self.checkTabBar()
87
88     def tabRemoved(self,index):
89         self.checkTabBar()
90
91     def checkTabBar(self):
92         if self.count()<2:
93             self.tabBar().hide()
94         else:
95             self.tabBar().show()
96
97 class PanelManager(QWidget):
98     def __init__(self, parent, devmgr=None):
99         QMainWindow.__init__(self, parent)
100         self.setObjectName("PanelManager")
101
102         # maps a device GUID to a QT panel
103         self.panels = {}
104
105         # a layout for ourselves
106         self.layout = QVBoxLayout(self)
107
108         # the tabs
109         self.tabs = OwnTabWidget(self)
110         self.tabs.hide()
111         self.layout.addWidget(self.tabs)
112
113         # a dialog that is shown during update
114         self.status = PanelManagerStatus(self)
115         self.layout.addWidget(self.status)
116         self.status.show()
117
118         self.devices = DeviceList( SYSTEM_CONFIG_FILE )
119         self.devices.updateFromFile( USER_CONFIG_FILE )
120
121         if devmgr is not None:
122             self.setManager(devmgr)
123
124     def setManager(self,devmgr):
125         self.devmgr = devmgr
126         self.devmgr.registerPreUpdateCallback(self.devlistPreUpdate)
127         self.devmgr.registerPostUpdateCallback(self.devlistPostUpdate)
128         self.devmgr.registerUpdateCallback(self.devlistUpdate)
129         self.devmgr.registerDestroyedCallback(self.devmgrDestroyed)
130         # create a timer to poll the panels
131         self.polltimer = QTimer()
132         self.connect( self.polltimer, SIGNAL('timeout()'), self.pollPanels )
133         self.polltimer.start( POLL_SLEEP_TIME_MSEC )
134
135         # create a timer to initialize the panel after the main form is shown
136         # since initialization can take a while
137         QTimer.singleShot( POLL_SLEEP_TIME_MSEC, self.updatePanels )
138
139         # live check timer
140         self.alivetimer = QTimer()
141         QObject.connect( self.alivetimer, SIGNAL('timeout()'), self.commCheck )
142         self.alivetimer.start( 2000 )
143
144     def count(self):
145         return self.tabs.count()
146
147     def pollPanels(self):
148         #log.debug("PanelManager::pollPanels()")
149         # only when not modifying the tabs
150         if self.tabs.isEnabled():
151             for guid in self.panels.keys():
152                 w = self.panels[guid]
153                 for child in w.children():
154                     #log.debug("poll child %s,%s" % (guid,child))
155                     if 'polledUpdate' in dir(child):
156                         try:
157                             child.polledUpdate()
158                         except:
159                             log.error("error in polled update")
160                             raise
161
162     def devlistPreUpdate(self):
163         log.debug("devlistPreUpdate")
164         self.tabs.setEnabled(False)
165         self.tabs.hide()
166         self.status.lblMessage.setText("Bus reconfiguration in progress, please wait...")
167         self.status.show()
168         #self.statusBar().showMessage("bus reconfiguration in progress...", 5000)
169
170     def devlistPostUpdate(self):
171         log.debug("devlistPostUpdate")
172         # this can fail if multiple busresets happen in fast succession
173         ntries = 10
174         while ntries > 0:
175             try:
176                 self.updatePanels()
177                 return
178             except:
179                 log.debug("devlistPostUpdate failed (%d)" % ntries)
180                 for guid in self.panels.keys():
181                     w = self.panels[guid]
182                     del self.panels[guid] # remove from the list
183                     idx = self.tabs.indexOf(w)
184                     self.tabs.removeTab(idx)
185                     del w # GC might also take care of that
186
187                 ntries = ntries - 1
188                 time.sleep(2) # sleep a few seconds
189
190         log.debug("devlistPostUpdate failed completely")
191         self.tabs.setEnabled(False)
192         self.tabs.hide()
193         self.status.lblMessage.setText("Error while reconfiguring. Please restart ffadomixer.")
194         self.status.show()
195
196
197     def devlistUpdate(self):
198         log.debug("devlistUpdate")
199
200     def devmgrDestroyed(self):
201         log.debug("devmgrDestroyed")
202         self.alivetimer.stop()
203         self.tabs.setEnabled(False)
204         self.tabs.hide()
205         self.status.lblMessage.setText("DBUS server was shut down, please restart it and restart ffadomixer...")
206         self.status.show()
207
208     def commCheck(self):
209         try:
210             nbDevices = self.devmgr.getNbDevices()
211         except:
212             log.debug("comms lost")
213             self.tabs.setEnabled(False)
214             self.tabs.hide()
215             self.status.lblMessage.setText("Failed to communicate with DBUS server. Please restart it and restart ffadomixer...")
216             self.status.show()
217             self.alivetimer.stop()
218
219     def updatePanels(self):
220         log.debug("PanelManager::updatePanels()")
221         nbDevices = self.devmgr.getNbDevices()
222         #self.statusBar().showMessage("Reconfiguring the mixer panels...")
223
224         # list of panels present
225         guids_with_tabs = self.panels.keys()
226
227         # build list of guids on the bus now
228         guids_present = []
229         guid_indexes = {}
230         for idx in range(nbDevices):
231             path = self.devmgr.getDeviceName(idx)
232             cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
233             guid = cfgrom.getGUID()
234             guids_present.append(guid)
235             guid_indexes[guid] = idx
236
237         # figure out what to remove
238         # the special panel (generic)
239         # that has (pseudo-)GUID 0
240         # is also automatically removed
241         to_remove = []
242         for guid in guids_with_tabs:
243             if not guid in guids_present:
244                 to_remove.append(guid)
245                 log.debug("going to remove %s" % str(guid))
246             else:
247                 log.debug("going to keep %s" % str(guid))
248
249         # figure out what to add
250         to_add = []
251         for guid in guids_present:
252             if not guid in guids_with_tabs:
253                 to_add.append(guid)
254                 log.debug("going to add %s" % str(guid))
255
256         # update the widget
257         for guid in to_remove:
258             w = self.panels[guid]
259             del self.panels[guid] # remove from the list
260             idx = self.tabs.indexOf(w)
261             self.tabs.removeTab(idx)
262             del w # GC might also take care of that
263
264         for guid in to_add:
265             # retrieve the device manager index
266             idx = guid_indexes[guid]
267             path = self.devmgr.getDeviceName(idx)
268             log.debug("Adding device %d: %s" % (idx, path))
269
270             cfgrom = ConfigRomInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
271             vendorId = cfgrom.getVendorId()
272             modelId = cfgrom.getModelId()
273             unitVersion = cfgrom.getUnitVersion()
274             guid = cfgrom.getGUID()
275             vendorName = cfgrom.getVendorName()
276             modelName = cfgrom.getModelName()
277             log.debug(" Found (%s, %X, %X) %s %s" % (str(guid), vendorId, modelId, vendorName, modelName))
278
279             # check whether this has already been registered at ffado.org
280             reg = ffado_registration(FFADO_VERSION, int(guid, 16),
281                                      vendorId, modelId,
282                                      vendorName, modelName)
283             reg.check_for_registration()
284
285             # The MOTU devices use unitVersion to differentiate models.  For the
286             # moment though we don't need to know precisely which model we're
287             # using.
288             if vendorId == 0x1f2:
289                 modelId = 0x00000000
290
291             dev = self.devices.getDeviceById( vendorId, modelId )
292
293             w = QWidget( )
294             l = QVBoxLayout( w )
295
296             # create a control object
297             hw = ControlInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+'/DeviceManager/'+path)
298             clockselect = ClockSelectInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path )
299             samplerateselect = SamplerateSelectInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path )
300             nickname = TextInterface( FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH+"/DeviceManager/"+path+"/Generic/Nickname" )
301
302             #
303             # Generic elements for all mixers follow here:
304             #
305             globalmixer = GlobalMixer( w )
306             globalmixer.configrom = cfgrom
307             globalmixer.clockselect = clockselect
308             globalmixer.samplerateselect = samplerateselect
309             globalmixer.nickname = nickname
310             globalmixer.hw = hw
311             globalmixer.initValues()
312             l.addWidget( globalmixer, 1 )
313
314             #
315             # Line to separate
316             #
317             l.addWidget( HLine( w ) )
318
319             #
320             # Specific (or dummy) mixer widgets get loaded in the following
321             #
322             if 'mixer' in dev and dev['mixer'] != None:
323                 mixerapp = dev['mixer']
324                 exec( "mixerwidget = "+mixerapp+"( w )" )
325             else:
326                 mixerwidget = DummyMixer( w )
327                 mixerapp = modelName+" (Dummy)"
328
329             #
330             # The same for all mixers
331             #
332             l.addWidget( mixerwidget, 10 )
333             mixerwidget.configrom = cfgrom
334             mixerwidget.clockselect = clockselect
335             mixerwidget.samplerateselect = samplerateselect
336             mixerwidget.nickname = nickname
337             mixerwidget.hw = hw
338             if 'buildMixer' in dir(mixerwidget):
339                 mixerwidget.buildMixer()
340             if 'initValues' in dir(mixerwidget):
341                 mixerwidget.initValues()
342             if 'getDisplayTitle' in dir(mixerwidget):
343                 title = mixerwidget.getDisplayTitle()
344             else:
345                 title = mixerapp
346
347             globalmixer.setName(title)
348             self.tabs.addTab( w, title )
349             self.panels[guid] = w
350
351         # if there is no panel, add the no-device message
352         # else make sure it is not present
353         if self.count() == 0:
354             self.tabs.hide()
355             self.tabs.setEnabled(False)
356             self.status.lblMessage.setText("No supported device found.")
357             self.status.show()
358             #self.statusBar().showMessage("No supported device found.", 5000)
359         else:
360             self.tabs.show()
361             self.tabs.setEnabled(True)
362             self.status.hide()
363             #self.statusBar().showMessage("Configured the mixer for %i devices." % self.tabs.count())
364             if use_generic:
365                 #
366                 # Show the generic (development) mixer if it is available
367                 #
368                 w = GenericMixer( devmgr.bus, FFADO_DBUS_SERVER, mw )
369                 self.tabs.addTab( w, "Generic Mixer" )
370                 self.panels[GUID_GENERIC_MIXER] = w
371
372     def busreset( self ):
373         QMessageBox.information( self, "Not supported", "Triggering bus resets from the mixer (via dbus) isn't yet supported." )
374
375 # vim: et
Note: See TracBrowser for help on using the browser.