root/branches/2.4.x/libffado/support/mixer-qt4/ffado/ffadowindow.py

Revision 2788, 10.7 kB (checked in by jwoithe, 4 years ago)

v2.4.x branch: merge r2784 from trunk.

ffado-mixer: prevent ffado-dbus-server holding the ffado-mixer lock socket.

If ffado-dbus-server is not running when ffado-mixer is started, ffado-mixer
will start it. On some systems, ffado-mixer could not be run a second time
after exiting if it needed to start ffado-dbus-server during its first run.
This has been reported by a small number of users over time, but it has been
difficult to debug because it did not happen on systems used by FFADO
developers.

Testing by Claudia Krelm indicates that on affected systems, the
subprocess.Popen() call passes file descriptors to the child process. If
this is done, ffado-dbus-server obtains a file descriptor to the unix socket
used as the ffado-mixer lock file. When ffado-mixer is stopped,
ffado-dbus-server still holds the lock's file descriptor open and therefore
the socket is not closed. In this scenario, the ffado-mixer lock file
(socket) would only be closed if ffado-dbus-server were shut down.

This was demonstrated on Claudia's system by explicitly requesting the
closure of file descriptors in the subprocess.Popen() call. Without this
change, ffado-mixer could not be restarted due to the continued presence of
the lock file even after ffado-mixer had been shut down. If file descriptor
closure was requested, ffado-mixer could be restarted whenever the previous
invocation had been exitted. On this basis, the bug can be fixed by
explicitly requesting file descriptors be closed when subprocess.Popen() is
used to spawn ffado-dbus-server. While many systems evidently do this by
default, there are clearly some which do not.

Thanks to Claudia Krelm for running the tests needed to identify the
problem.

Line 
1 # Copyright (C) 2005-2008 by Pieter Palmers
2 #               2007-2009 by Arnold Krille
3 #
4 # This file is part of FFADO
5 # FFADO = Free Firewire (pro-)audio drivers for linux
6 #
7 # FFADO is based upon FreeBoB.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 import ctypes
24 import datetime
25 import os
26
27 from ffado.config import *
28
29 import subprocess
30
31 # from PyQt4.QtCore import QObject, QTimer, Qt
32 # from PyQt4.QtGui import *
33 from ffado.import_pyqt import *
34
35 from ffado.dbus_util import *
36
37 from ffado.panelmanager import PanelManager
38
39 from ffado.logginghandler import *
40
41 """Just a small helper to ask the retry-question without a blocking messagebox"""
42 class StartDialog(QWidget):
43     def __init__(self, parent):
44         QWidget.__init__(self, parent)
45         self.setObjectName("Restart Dialog")
46         self.label = QLabel("<qt>Somehow the connection to the dbus-service of FFADO couldn't be established.<p>\nShall we take another try?</qt>",self)
47         self.button = QPushButton("Retry", self)
48         self.layout = QGridLayout(self)
49         self.layout.setContentsMargins( 50, 10, 50, 10 )
50         self.layout.addWidget(self.label, 0, 0, Qt.AlignHCenter|Qt.AlignBottom)
51         self.layout.addWidget(self.button, 1, 0, Qt.AlignHCenter|Qt.AlignTop)
52
53 class FFADOWindow(QMainWindow):
54     def __init__(self, parent):
55         QMainWindow.__init__(self, parent)
56
57         self.textlogger = QTextLogger(self)
58         dock = QDockWidget("Log Messages",self)
59         dock.setWidget(self.textlogger.textedit)
60         logging.getLogger('').addHandler(self.textlogger)
61         self.addDockWidget(Qt.BottomDockWidgetArea, dock)
62
63         self.statuslogger = QStatusLogger(self, self.statusBar(), 20)
64         logging.getLogger('').addHandler(self.statuslogger)
65
66         self.manager = PanelManager(self)
67         self.manager.connectionLost.connect(self.connectToDBUS)
68
69         filemenu = self.menuBar().addMenu("&File")
70         self.openaction = QAction(QIcon.fromTheme("document-open"),"&Open", self)
71         self.openaction.setShortcut(self.tr("Ctrl+O"))
72         self.openaction.setEnabled(False)
73         self.openaction.triggered.connect(self.manager.readSettings)
74         filemenu.addAction(self.openaction)
75         self.saveaction = QAction(QIcon.fromTheme("document-save-as"),"&Save as...", self)
76         self.saveaction.setShortcut(self.tr("Ctrl+S"))
77         self.saveaction.setEnabled(False)
78         self.saveaction.triggered.connect(self.manager.saveSettings)
79         filemenu.addAction(self.saveaction)
80         self.quitaction = QAction(QIcon.fromTheme("application-exit"),"&Quit", self)
81         self.quitaction.setShortcut(self.tr("Ctrl+q"))
82         self.quitaction.triggered.connect(self.close)
83         filemenu.addAction(self.quitaction)
84
85         self.editmenu = self.menuBar().addMenu("&View")
86
87         self.thememenu = self.editmenu.addMenu("Theme")
88         themes = QStyleFactory.keys()
89         self.menuTheme = {}
90         for theme in themes:
91             self.menuTheme[theme] = QAction(QIcon.fromTheme("preferences-desktop-theme"), theme, self )
92             self.menuTheme[theme].setCheckable(True)
93
94             if (ffado_python3 and (self.style().objectName().lower() == theme.lower()) or
95                     not(ffado_python3) and (self.style().objectName().toLower() == theme.toLower() if ffado_pyqt_version == 4 else
96                                             self.style().objectName().lower() == theme.lower())):
97                 self.menuTheme[theme].setDisabled(True)
98                 self.menuTheme[theme].setChecked(True)
99             self.menuTheme[theme].triggered.connect(self.switchTheme )
100             self.thememenu.addAction( self.menuTheme[theme] )
101
102         self.updateaction = QAction(QIcon.fromTheme("view-refresh"),"&Update Mixer Panels", self)
103         self.updateaction.setEnabled(False)
104         self.updateaction.triggered.connect(self.manager.updatePanels)
105         self.editmenu.addAction(self.updateaction)
106         self.refreshaction = QAction(QIcon.fromTheme("view-refresh"),"&Refresh Current Panels", self)
107         self.refreshaction.triggered.connect(self.manager.refreshPanels)
108         self.editmenu.addAction(self.refreshaction)
109         self.editmenu.addSeparator()
110         self.devices = {}
111
112         helpmenu = self.menuBar().addMenu( "&Help" )
113         self.aboutaction = QAction(QIcon.fromTheme("help-about"), "About &FFADO", self )
114         self.aboutaction.triggered.connect(self.aboutFFADO)
115         helpmenu.addAction( self.aboutaction )
116         self.aboutqtaction = QAction(QIcon.fromTheme("help-about"),  "About &Qt", self )
117         self.aboutqtaction.triggered.connect(QApplication.instance().aboutQt)
118         helpmenu.addAction( self.aboutqtaction )
119
120         log.info( "Starting up" )
121
122         QTimer.singleShot( 1, self.tryStartDBUSServer )
123
124     def __del__(self):
125         log.info("__del__")
126         del self.manager
127         log.info("__del__ finished")
128
129     def switchTheme(self, checked) :
130         for theme in self.menuTheme :
131             if not self.menuTheme[theme].isEnabled() :
132                 self.menuTheme[theme].setChecked(False)
133                 self.menuTheme[theme].setDisabled(False)
134         for theme in self.menuTheme :
135             if self.menuTheme[theme].isChecked() :
136                 self.menuTheme[theme].setDisabled(True)
137                 QApplication.setStyle(QStyleFactory.create(theme))
138
139     def closeEvent(self, event):
140         log.info("closeEvent()")
141         event.accept()
142
143     def connectToDBUS(self):
144         log.info("connectToDBUS")
145         try:
146             self.setupDeviceManager()
147         except dbus.DBusException as ex:
148             log.error("Could not communicate with the FFADO DBus service...")
149             if not hasattr(self,"retry"):
150                 self.retry = StartDialog(self)
151                 self.retry.button.clicked.connect(self.tryStartDBUSServer)
152             if hasattr(self, "retry"):
153                 self.manager.setParent(None)
154                 self.setCentralWidget(self.retry)
155                 self.retry.setEnabled(True)
156
157     def tryStartDBUSServer(self):
158         try:
159             self.setupDeviceManager()
160         except dbus.DBusException as ex:
161             if hasattr(self, "retry"):
162                 self.retry.setEnabled(False)
163             subprocess.Popen(['ffado-dbus-server', '-v3'], close_fds=True).pid
164             QTimer.singleShot(5000, self.connectToDBUS)
165
166     def setupDeviceManager(self):
167         devmgr = DeviceManagerInterface(FFADO_DBUS_SERVER, FFADO_DBUS_BASEPATH)
168         self.manager.setManager(devmgr)
169         if hasattr(self, "retry"):
170             self.retry.setParent(None)
171         self.setCentralWidget(self.manager)
172         self.updateaction.setEnabled(True)
173
174     def aboutFFADO(self):
175         QMessageBox.about( self, "About FFADO", """
176 <h1>ffado.org</h1>
177
178 <p>{ffado_version}</p>
179
180 <p>FFADO is the new approach to have firewire audio on linux.</p>
181
182         <p>&copy; 2006-2018 by the FFADO developers<br />ffado is licensed under the GPLv3, for the full license text see <a href="http://www.gnu.org/licenses/">www.gnu.org/licenses</a> or the LICENSE.* files shipped with ffado.</p>
183
184 <p>FFADO developers are:<ul>
185 <li>Pieter Palmers
186 <li>Daniel Wagner
187 <li>Jonathan Woithe
188 <li>Arnold Krille
189 <li>Philippe Carriere
190 <li>Takashi Sakamoto
191 </ul>
192 with contributions from:<ul>
193 <li>Adrian Knoth
194 <li>Stefan Richter
195 <li>Jano Svitok
196 </ul>
197         """.format(ffado_version=get_ffado_version(), thisyear=datetime.datetime.now().year))
198
199 def get_ffado_version():
200     try:
201         # call the C function ffado_get_version() to figure out the version
202         lib = ctypes.cdll.LoadLibrary('libffado.so')
203         func = ctypes.CFUNCTYPE(ctypes.c_char_p)
204         ffado_get_version = func(('ffado_get_version', lib))
205         return ffado_get_version()
206     except:
207         return "libffado"
208
209 def get_lock(process_name):
210     import socket
211     import sys
212
213     global lock_socket
214     lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
215     try:
216         lock_socket.bind('\0' + process_name)
217         # Lock acquired
218     except socket.error:
219         print( 'ffado-mixer instance is already running' )
220         sys.exit()
221
222
223 def ffadomain(args):
224     #set up logging
225     import logging
226     logging.basicConfig( datefmt="%H:%M:%S", format="%(asctime)s %(name)-16s %(levelname)-8s %(message)s" )
227
228     if DEBUG:
229         debug_level = logging.DEBUG
230     else:
231         debug_level = logging.INFO
232
233     get_lock('ffado-mixer')
234
235     # Very simple command line option parser
236     if (len(args) > 1) and (args[1] == "-b" or args[1] == "--bypassdbus"):
237         ffado.config.bypassdbus = True
238
239     # main loggers:
240     logging.getLogger('main').setLevel(debug_level)
241     logging.getLogger('dbus').setLevel(debug_level)
242     logging.getLogger('registration').setLevel(debug_level)
243     logging.getLogger('panelmanager').setLevel(debug_level)
244     logging.getLogger('configparser').setLevel(logging.INFO)
245
246     # widgets:
247     logging.getLogger('matrixmixer').setLevel(debug_level)
248     logging.getLogger('crossbarrouter').setLevel(debug_level)
249
250     # mixers:
251     logging.getLogger('audiofire').setLevel(debug_level)
252     logging.getLogger('bridgeco').setLevel(debug_level)
253     logging.getLogger('edirolfa101').setLevel(debug_level)
254     logging.getLogger('edirolfa66').setLevel(debug_level)
255     logging.getLogger('motu').setLevel(debug_level)
256     logging.getLogger('rme').setLevel(debug_level)
257     logging.getLogger('phase24').setLevel(debug_level)
258     logging.getLogger('phase88').setLevel(debug_level)
259     logging.getLogger('quatafire').setLevel(debug_level)
260     logging.getLogger('saffirebase').setLevel(debug_level)
261     logging.getLogger('saffire').setLevel(debug_level)
262     logging.getLogger('saffirepro').setLevel(debug_level)
263
264     logging.getLogger('global').setLevel(debug_level)
265
266     log = logging.getLogger('main')
267     log.debug("Using %s with Qt: %s PyQt: %s" % (get_ffado_version(), QtCore.QT_VERSION_STR, QtCore.PYQT_VERSION_STR))
268
269     app = QApplication(args)
270     app.setWindowIcon( QIcon( SHAREDIR + "/icons/hi64-apps-ffado.png" ) )
271
272     app.setOrganizationName("FFADO")
273     app.setOrganizationDomain("ffado.org")
274     app.setApplicationName("ffado-mixer")
275
276     mainwindow = FFADOWindow(None)
277
278
279     # rock & roll
280     mainwindow.show()
281     return app.exec_()
282
283 if __name__ == "__main__":
284     import sys
285     sys.exit(ffadomain(sys.argv))
286
287 #
288 # vim: ts=4 sw=4 et
Note: See TracBrowser for help on using the browser.