I'm trying to get a D-Bus signal handler to be called whenever the state of a sink changes in PulseAudio (e.g. becomes inactive). Unfortunately, it isn't being called and I frankly am not sure why.
import dbus
import dbus.mainloop.glib
from gi.repository import GObject
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
def signal_handler(*args, **kwargs):
print('sig: ', args, kwargs)
def connect():
import os
if 'PULSE_DBUS_SERVER' in os.environ:
address = os.environ['PULSE_DBUS_SERVER']
else:
bus = dbus.SessionBus()
server_lookup = bus.get_object("org.PulseAudio1", "/org/pulseaudio/server_lookup1")
address = server_lookup.Get("org.PulseAudio.ServerLookup1", "Address", dbus_interface="org.freedesktop.DBus.Properties")
return dbus.connection.Connection(address)
conn = connect()
core = conn.get_object(object_path='/org/pulseaudio/core1')
core.connect_to_signal('StateUpdated', signal_handler)
core.ListenForSignal('org.PulseAudio.Core1.Device.StateUpdated', dbus.Array(signature='o'), dbus_interface='org.PulseAudio.Core1')
loop = GObject.MainLoop()
loop.run()
D-Bus works by sending messages between processes. If you're using a sufficiently high-level binding, you may never work with messages directly. There are 4 message types: Method call messages ask to invoke a method on an object.
The dbus-send command is used to send a message to a D-Bus message bus. See https://www.freedesktop.org/wiki/Software/dbus/ for more information about the big picture.
Register service The dbus-daemon scan the /usr/share/dbus-1/system-services for system services. We should create a simple service file there. The service file has to be named after bus name of the service. Remark: ${USER} should be replaced with username which is used in the policy file.
dbus-python is a legacy API, built with a deprecated dbus-glib library, and involving a lot of type-guessing (despite "explicit is better than implicit" and "resist the temptation to guess"). txdbus is a native Python implementation of the D-Bus protocol for the Twisted networking framework.
Try this, works for me.
import dbus
import os
from dbus.mainloop.glib import DBusGMainLoop
import gobject
def pulse_bus_address():
if 'PULSE_DBUS_SERVER' in os.environ:
address = os.environ['PULSE_DBUS_SERVER']
else:
bus = dbus.SessionBus()
server_lookup = bus.get_object("org.PulseAudio1", "/org/pulseaudio/server_lookup1")
address = server_lookup.Get("org.PulseAudio.ServerLookup1", "Address", dbus_interface="org.freedesktop.DBus.Properties")
print(address)
return address
def sig_handler(state):
print("State changed to %s" % state)
if state == 0:
print("Pulseaudio running.")
elif state == 1:
print("Pulseaudio idle.")
elif state == 2:
print("Pulseaudio suspended")
# setup the glib mainloop
DBusGMainLoop(set_as_default=True)
loop = gobject.MainLoop()
pulse_bus = dbus.connection.Connection(pulse_bus_address())
pulse_core = pulse_bus.get_object(object_path='/org/pulseaudio/core1')
pulse_core.ListenForSignal('org.PulseAudio.Core1.Device.StateUpdated', dbus.Array(signature='o'), dbus_interface='org.PulseAudio.Core1')
pulse_bus.add_signal_receiver(sig_handler, 'StateUpdated')
loop.run()
Requires pulseaudio's default.pa to have the following:
.ifexists module-dbus-protocol.so
load-module module-dbus-protocol
.endif
Edit:
for those wondering about @conf-f-use's question concerning the application name. It turns out that they answered this issue themselves and posted the answer here: https://askubuntu.com/questions/906160/is-there-a-way-to-detect-whether-a-skype-call-is-in-progress-dbus-pulseaudio
Stealing a section of @con-f-use's code and applying to my code above, we get a monitor that tracks what the state is and is able to tell you the application name, the artist, title and name of what is playing.
Cheers @con-f-use :)
import dbus
import os
from dbus.mainloop.glib import DBusGMainLoop
import gobject
def pulse_bus_address():
if 'PULSE_DBUS_SERVER' in os.environ:
address = os.environ['PULSE_DBUS_SERVER']
else:
bus = dbus.SessionBus()
server_lookup = bus.get_object("org.PulseAudio1", "/org/pulseaudio/server_lookup1")
address = server_lookup.Get("org.PulseAudio.ServerLookup1", "Address", dbus_interface="org.freedesktop.DBus.Properties")
print(address)
return address
# convert byte array to string
def dbus2str(db):
if type(db)==dbus.Struct:
return str(tuple(dbus2str(i) for i in db))
if type(db)==dbus.Array:
return "".join([dbus2str(i) for i in db])
if type(db)==dbus.Dictionary:
return dict((dbus2str(k), dbus2str(v)) for k, v in db.items())
if type(db)==dbus.String:
return db+''
if type(db)==dbus.UInt32:
return str(db+0)
if type(db)==dbus.Byte:
return chr(db)
if type(db)==dbus.Boolean:
return db==True
if type(db)==dict:
return dict((dbus2str(k), dbus2str(v)) for k, v in db.items())
return "(%s:%s)" % (type(db), db)
def sig_handler(state):
print("State changed to %s" % state)
if state == 0:
print("Pulseaudio running.")
elif state == 1:
print("Pulseaudio idle.")
elif state == 2:
print("Pulseaudio suspended")
dbus_pstreams = (
dbus.Interface(
pulse_bus.get_object(object_path=path),
dbus_interface='org.freedesktop.DBus.Properties'
) for path in pulse_core.Get(
'org.PulseAudio.Core1',
'PlaybackStreams',
dbus_interface='org.freedesktop.DBus.Properties' )
)
pstreams = {}
for pstream in dbus_pstreams:
try:
pstreams[pstream.Get('org.PulseAudio.Core1.Stream', 'Index')] = pstream
except dbus.exceptions.DBusException:
pass
if pstreams:
for stream in pstreams.keys():
plist = pstreams[stream].Get('org.PulseAudio.Core1.Stream', 'PropertyList')
appname = dbus2str(plist.get('application.name', None))
artist = dbus2str(plist.get('media.artist', None))
title = dbus2str(plist.get('media.title', None))
name = dbus2str(plist.get('media.name', None))
print appname,artist,title,name
# setup the glib mainloop
DBusGMainLoop(set_as_default=True)
loop = gobject.MainLoop()
pulse_bus = dbus.connection.Connection(pulse_bus_address())
pulse_core = pulse_bus.get_object(object_path='/org/pulseaudio/core1')
#pulse_clients = pulse_bus.get_object(object_path='/org/pulseaudio/core1/Clients')
#print dir(pulse_clients)
pulse_core.ListenForSignal('org.PulseAudio.Core1.Device.StateUpdated', dbus.Array(signature='o'), dbus_interface='org.PulseAudio.Core1')
pulse_bus.add_signal_receiver(sig_handler, 'StateUpdated')
loop.run()
I my environment, I have dbus
and pulseaudio
running, however the discovered address does not exist:
>>> import dbus
>>> import dbus.mainloop.glib
>>> from gi.repository import GObject
>>> dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
<dbus.mainloop.NativeMainLoop object at 0x7f3c98ffd4e0>
>>> bus = dbus.SessionBus()
>>> server_lookup = bus.get_object("org.PulseAudio1", "/org/pulseaudio/server_lookup1")
>>> address = server_lookup.Get("org.PulseAudio.ServerLookup1", "Address", dbus_interface="org.freedesktop.DBus.Properties")
>>> address
dbus.String('unix:path=/run/user/1000/pulse/dbus-socket', variant_level=1)
$ dbus-monitor --address 'unix:path=/run/user/1000/pulse/dbus-socket'
Failed to open connection to unix:path=/run/user/1000/pulse/dbus-socket: Failed to connect to socket /run/user/1000/pulse/dbus-socket: No such file or directory
$ ls /run/user/1000/pulse/
cli native pid
I don't know if my configuration is default, but it seems dbus integration is just not there!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With