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 | | |
---|
| 62 | # |
---|