~ubuntu-branches/ubuntu/vivid/blueman/vivid-proposed

« back to all changes in this revision

Viewing changes to .pc/02_dont_crash_on_non-bluetooth_card.patch/blueman/plugins/applet/PulseAudio.py

  • Committer: Package Import Robot
  • Author(s): Artur Rona
  • Date: 2014-12-24 18:33:36 UTC
  • mfrom: (2.3.8 sid)
  • Revision ID: package-import@ubuntu.com-20141224183336-cyb82ot0y8tz8flq
Tags: 1.99~alpha1-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - debian/patches/01_dont_autostart_lxde.patch:
    + Don't autostart the applet in LXDE.
  - debian/patches/03_filemanager_fix.patch:
    + Add support for more filemanagers.
* debian/patches/02_dont_crash_on_non-bluetooth_card.patch:
  - Dropped, no longer applicable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008 Valmantas Paliksa <walmis at balticum-tv dot lt>
2
 
# Copyright (C) 2008 Tadas Dailyda <tadas at dailyda dot com>
3
 
#
4
 
# Licensed under the GNU General Public License Version 3
5
 
#
6
 
# This program is free software: you can redistribute it and/or modify
7
 
# it under the terms of the GNU General Public License as published by
8
 
# the Free Software Foundation, either version 3 of the License, or
9
 
# (at your option) any later version.
10
 
#
11
 
# This program is distributed in the hope that it will be useful,
12
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
# GNU General Public License for more details.
15
 
#
16
 
# You should have received a copy of the GNU General Public License
17
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 
19
 
from blueman.Functions import *
20
 
from blueman.plugins.AppletPlugin import AppletPlugin
21
 
from blueman.bluez.Device import Device as BluezDevice
22
 
from blueman.main.Device import Device
23
 
from blueman.gui.Notification import Notification
24
 
from blueman.main.PulseAudioUtils import PulseAudioUtils, EventType
25
 
from subprocess import Popen, PIPE
26
 
import gobject
27
 
 
28
 
import dbus
29
 
from blueman.main.SignalTracker import SignalTracker
30
 
 
31
 
class SourceRedirector:
32
 
        instances = []
33
 
        def __init__(self, module_id, device_path, pa_utils):
34
 
                if module_id in SourceRedirector.instances:
35
 
                        return
36
 
                else:
37
 
                        SourceRedirector.instances.append(module_id)
38
 
                
39
 
                self.module_id = module_id
40
 
                self.pa_utils = pa_utils
41
 
                self.device = Device(device_path)
42
 
                self.signals = SignalTracker()
43
 
                self.bus = dbus.SystemBus()
44
 
                self.signals.Handle("dbus", self.bus, self.on_source_prop_change, "PropertyChanged", "org.bluez.AudioSource", path=device_path)
45
 
                
46
 
                self.pacat = None
47
 
                self.parec = None
48
 
                self.loopback_id = None
49
 
                
50
 
                dprint("Starting source redirector")
51
 
                def sources_cb(sources):
52
 
                        for k, v in sources.iteritems():
53
 
                                props = v["proplist"]
54
 
                                if "bluetooth.protocol" in props:
55
 
                                        if props["bluetooth.protocol"] == "a2dp_source":
56
 
                                                if v["owner_module"] == self.module_id:
57
 
                                                        dprint("Found source", k)
58
 
                                                        self.start_redirect(k)
59
 
                                                        return
60
 
                        dprint("Source not found :(")
61
 
                
62
 
                self.pa_utils.ListSources(sources_cb)
63
 
                
64
 
        def start_redirect(self, source):
65
 
 
66
 
                def on_load(res):
67
 
                        dprint("module-loopback load result", res)
68
 
                        if res < 0:
69
 
                                self.parec = Popen(["parec", "-d", str(source)], stdout=PIPE)
70
 
                                self.pacat = Popen(["pacat", "--client-name=Blueman", "--stream-name=%s" % self.device.Address, "--property=application.icon_name=blueman"], stdin=self.parec.stdout)
71
 
                        else:
72
 
                                self.loopback_id = res
73
 
                                
74
 
                self.pa_utils.LoadModule("module-loopback", "source=%d" % source, on_load)
75
 
                
76
 
        def on_source_prop_change(self, key, value):
77
 
                if key == "State":
78
 
                        if value == "disconnected":
79
 
                                if self.pacat:
80
 
                                        self.pacat.terminate()
81
 
                                if self.parec:
82
 
                                        self.parec.terminate()
83
 
                                if self.loopback_id:
84
 
                                        self.pa_utils.UnloadModule(self.loopback_id, lambda x: dprint("Loopback module unload result", x))
85
 
                                
86
 
                                self.signals.DisconnectAll()
87
 
                                
88
 
                                SourceRedirector.instances.remove(self.module_id)
89
 
 
90
 
                                del self.pa_utils
91
 
                                
92
 
        def __del__(self):
93
 
                dprint("Destroying redirector")
94
 
                
95
 
class Module(gobject.GObject):
96
 
        __gsignals__ = {
97
 
                'loaded' : (gobject.SIGNAL_NO_HOOKS, gobject.TYPE_NONE, ()),
98
 
        }       
99
 
        def __init__(self):
100
 
                gobject.GObject.__init__(self)
101
 
                self.refcount = 0
102
 
                self.id = None
103
 
                
104
 
        def unload(self):
105
 
                dprint(self.id)
106
 
                pa = PulseAudioUtils()
107
 
                id = self.id
108
 
                pa.UnloadModule(self.id, lambda x: dprint("Unload %s result %s" % (id, x)))
109
 
                self.id = None
110
 
                self.refcount = 0
111
 
                
112
 
        def ref(self):
113
 
                self.refcount+=1
114
 
                
115
 
                dprint(self.id, self.refcount)
116
 
                
117
 
        def unref(self):
118
 
                self.refcount-=1
119
 
                
120
 
                dprint(self.id, self.refcount)
121
 
                
122
 
                if self.refcount <= 0 and self.id:
123
 
                        self.unload()
124
 
                        
125
 
        def load(self, args, cb):
126
 
                if self.id != None:
127
 
                        self.unload()
128
 
                
129
 
                def load_cb(res):
130
 
                        if res > 0:
131
 
                                self.refcount = 1
132
 
                                self.id = res
133
 
                                if cb:
134
 
                                        cb(res)
135
 
                                self.emit("loaded")
136
 
                        else:
137
 
                                self.refcount = 0
138
 
                                self.id = None
139
 
                
140
 
                PulseAudioUtils().LoadModule("module-bluetooth-device",
141
 
                                                                         args,
142
 
                                                                         load_cb)
143
 
 
144
 
class PulseAudio(AppletPlugin):
145
 
        __author__ = "Walmis"
146
 
        __description__ = _("Automatically manages Pulseaudio Bluetooth sinks/sources.\n"
147
 
                                                "<b>Note:</b> Requires pulseaudio 0.9.15 or higher")
148
 
        __icon__ = "audio-card"
149
 
        __options__  = {
150
 
                "checked" : {"type": bool, "default": False},
151
 
                "make_default_sink": {"type":bool, 
152
 
                                                          "default": True, 
153
 
                                                          "name": _("Make default sink"), 
154
 
                                                          "desc": _("Make the a2dp audio sink the default after connection")},
155
 
                "move_streams": {"type": bool, 
156
 
                                                 "default": True, 
157
 
                                                 "name": _("Move streams"), 
158
 
                                                 "desc": _("Move existing audio streams to bluetooth device")}
159
 
        }
160
 
        def on_load(self, applet):
161
 
                self.signals = SignalTracker()
162
 
                if not self.get_option("checked"):
163
 
                        self.set_option("checked", True)
164
 
                        if not have("pactl"):
165
 
                                applet.Plugins.SetConfig("PulseAudio", False)
166
 
                                return
167
 
                        
168
 
                self.bus = dbus.SystemBus()
169
 
                
170
 
                self.connected_sources = []
171
 
                self.connected_sinks = []
172
 
                self.connected_hs = []
173
 
                
174
 
                self.loaded_modules = {}
175
 
                
176
 
                self.pulse_utils = PulseAudioUtils()
177
 
                version = self.pulse_utils.GetVersion()
178
 
                dprint("PulseAudio version:", version)
179
 
                
180
 
                if version[0] == 0:
181
 
                        if tuple(version) < (0, 9, 15):
182
 
                                raise Exception("PulseAudio too old, required 0.9.15 or higher")
183
 
                
184
 
                self.signals.Handle("dbus", 
185
 
                                                        self.bus, 
186
 
                                                        self.on_sink_prop_change, 
187
 
                                                        "PropertyChanged", 
188
 
                                                        "org.bluez.AudioSink", 
189
 
                                                        path_keyword="device")
190
 
                                                        
191
 
                self.signals.Handle("dbus", 
192
 
                                                        self.bus, 
193
 
                                                        self.on_source_prop_change, 
194
 
                                                        "PropertyChanged", 
195
 
                                                        "org.bluez.AudioSource", 
196
 
                                                        path_keyword="device")
197
 
                                                        
198
 
                self.signals.Handle("dbus", 
199
 
                                                        self.bus, 
200
 
                                                        self.on_hsp_prop_change, 
201
 
                                                        "PropertyChanged", 
202
 
                                                        "org.bluez.Headset", 
203
 
                                                        path_keyword="device")
204
 
                
205
 
 
206
 
                self.signals.Handle(self.pulse_utils, "event", self.on_pulse_event)
207
 
        
208
 
        def on_pulse_event(self, pa_utils, event, idx):
209
 
                if (EventType.CARD | EventType.CHANGE) == event:
210
 
                        dprint(event)
211
 
                        def card_cb(c):
212
 
                                dprint(c)
213
 
                                m = self.loaded_modules[c["proplist"]["bluez.path"]]
214
 
                                if c["owner_module"] == m.id:
215
 
                                        if c["active_profile"] == "a2dp_source":
216
 
                                                SourceRedirector(m.id, c["proplist"]["bluez.path"], pa_utils)
217
 
                        
218
 
                        pa_utils.GetCard(idx, card_cb)
219
 
                
220
 
        
221
 
        def on_unload(self):
222
 
                self.signals.DisconnectAll()
223
 
                
224
 
        def load_module(self, dev_path, args, cb=None):
225
 
                if not dev_path in self.loaded_modules:
226
 
                        m = Module()
227
 
                        m.load(args, cb)
228
 
                        self.loaded_modules[dev_path] = m
229
 
                else:
230
 
                        self.loaded_modules[dev_path].ref()
231
 
                
232
 
        def try_unload_module(self, dev_path):
233
 
                try:
234
 
                        m = self.loaded_modules[dev_path]
235
 
                        m.unref()
236
 
                        if m.refcount == 0:
237
 
                                del self.loaded_modules[dev_path]
238
 
                except Exception, e:
239
 
                        dprint(e)
240
 
                
241
 
        def on_source_prop_change(self, key, value, device):
242
 
                dprint(key, value)
243
 
                        
244
 
                if key == "State":
245
 
                        if value == "connected":
246
 
                                if not device in self.connected_sources:
247
 
                                        self.connected_sources.append(device)
248
 
                                        d = Device(device)
249
 
                                        self.load_module(device, "path=%s address=%s profile=a2dp_source source_properties=device.icon_name=blueman card_properties=device.icon_name=blueman" % (device, d.Address))
250
 
                                        
251
 
                        elif value == "disconnected":
252
 
                                self.try_unload_module(device)
253
 
                                if device in self.connected_sources:
254
 
                                        self.connected_sources.remove(device)
255
 
 
256
 
                        elif value == "playing":
257
 
                                try:
258
 
                                        m = self.loaded_modules[device]
259
 
                                        def on_loaded(m):
260
 
                                                SourceRedirector(m.id, device, self.pulse_utils)
261
 
                                                m.disconnect(sig)
262
 
                                        
263
 
                                        if not m.id:
264
 
                                                sig = m.connect("loaded", on_loaded)
265
 
                                        else:
266
 
                                                SourceRedirector(m.id, device, self.pulse_utils)
267
 
                                        
268
 
                                except Exception, e:
269
 
                                        dprint(e)
270
 
                                
271
 
                
272
 
        def on_sink_prop_change(self, key, value, device):
273
 
                if key == "Connected" and value:
274
 
                        if not device in self.connected_sinks:
275
 
                                self.connected_sinks.append(device)
276
 
                                gobject.timeout_add(500, self.setup_pa, device, "a2dp")
277
 
                
278
 
                elif key == "Connected" and not value:
279
 
                        if device in self.connected_sinks:
280
 
                                self.connected_sinks.remove(device)
281
 
                        self.try_unload_module(device)
282
 
                
283
 
        def on_hsp_prop_change(self, key, value, device):
284
 
                if key == "Connected" and value:
285
 
                        if not device in self.connected_hs:
286
 
                                self.connected_hs.append(device)
287
 
                                self.setup_pa(device, "hsp")
288
 
                
289
 
                elif key == "Connected" and not value:
290
 
                        self.try_unload_module(device)
291
 
                        if device in self.connected_hs:
292
 
                                self.connected_hs.remove(device)
293
 
                        
294
 
        def move_pa_streams(self, sink_id):
295
 
                def inputs_cb(inputs):
296
 
                        for k, v in inputs.iteritems():
297
 
                                dprint("moving stream", v["name"], "to sink", sink_id)
298
 
                                self.pulse_utils.MoveSinkInput(k, sink_id, None)
299
 
                
300
 
                self.pulse_utils.ListSinkInputs(inputs_cb)
301
 
                                
302
 
        def setup_pa_sinks(self, module_id):
303
 
                dprint("module", module_id)
304
 
                
305
 
                def sinks_cb(sinks):
306
 
                        for k, v in sinks.iteritems():
307
 
                                if v["owner_module"] == module_id:
308
 
                                        if self.get_option("make_default_sink"):
309
 
                                                dprint("Making sink", v["name"], "the default")
310
 
                                                self.pulse_utils.SetDefaultSink(v["name"], None)
311
 
                                        if self.get_option("move_streams"):
312
 
                                                self.move_pa_streams(k)
313
 
                                        
314
 
                
315
 
                self.pulse_utils.ListSinks(sinks_cb)
316
 
                
317
 
        def setup_pa(self, device_path, profile):
318
 
                device = Device(device_path)
319
 
                
320
 
                
321
 
                def load_cb(res):
322
 
                        dprint("Load result", res)
323
 
 
324
 
                        if res < 0:
325
 
                                Notification(_("Bluetooth Audio"), 
326
 
                                                         _("Failed to initialize PulseAudio Bluetooth module. Bluetooth audio over PulseAudio will not work."), 
327
 
                                                         pixbuf=get_notification_icon("gtk-dialog-error"), 
328
 
                                                         status_icon=self.Applet.Plugins.StatusIcon)                            
329
 
                        else:
330
 
                                Notification(_("Bluetooth Audio"), 
331
 
                                                         _("Successfully connected to a Bluetooth audio device. This device will now be available in the PulseAudio mixer"), 
332
 
                                                         pixbuf=get_notification_icon("audio-card"), 
333
 
                                                         status_icon=self.Applet.Plugins.StatusIcon)    
334
 
                                if profile == "a2dp":                    
335
 
                                        self.setup_pa_sinks(res)
336
 
                        
337
 
                        #connect to other services, so pulseaudio profile switcher could work
338
 
                        for s in ("headset", "audiosink", "audiosource"):       
339
 
                                try:
340
 
                                        device.Services[s].Connect()
341
 
                                except KeyError:
342
 
                                        pass
343
 
                                except Exception, e:
344
 
                                        print e
345
 
                                
346
 
                version = self.pulse_utils.GetVersion()
347
 
                if version[0] == 1 or version[2] >= 18:
348
 
                        args = "address=%s profile=%s sink_properties=device.icon_name=blueman card_properties=device.icon_name=blueman"
349
 
                else:
350
 
                        args = "address=%s profile=%s"
351
 
 
352
 
                self.load_module(device_path, args % (device.Address, profile), load_cb)
353
 
                
354
 
                
355