Skip to content

Instantly share code, notes, and snippets.

@hramrach
Last active December 1, 2015 10:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hramrach/606ae05725f49eeaabf3 to your computer and use it in GitHub Desktop.
Save hramrach/606ae05725f49eeaabf3 to your computer and use it in GitHub Desktop.
#include <Eldbus.h>
#include <Ecore.h>
#define BLUEZ_SERVICE "org.bluez"
#define OWN_SERVICE "test.testenkrumppel"
#define OWN_OBJECT "/test/testenkrumppel/SourceEndpoint"
#define MANAGER "org.freedesktop.DBus.ObjectManager"
#define MEDIA "org.bluez.Media1"
#define TRANSPORT "org.bluez.MediaTransport1"
#define ENDPOINT "org.bluez.MediaEndpoint1"
#define ADAPTER "org.bluez.Adapter1"
#define LOGNAME "BTTest"
#define LOGCOLOR EINA_COLOR_CYAN
static int log_dom = -1;
#define ERR(...) EINA_LOG_DOM_ERR(log_dom, __VA_ARGS__)
#define NTC(...) EINA_LOG_DOM_ERR(log_dom, __VA_ARGS__) //LOG_DOM_INFO is not working
static Eldbus_Connection *conn = NULL;
static Ecore_Timer *timer = NULL;
static Eina_List *interfaces = NULL;
static Eldbus_Service_Interface* srv = NULL;
static void on_endpoint_register(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
const char *errname, *errmsg, *sig = eldbus_message_signature_get(msg);
NTC("Endpoint Registered %s", sig);
if (eldbus_message_error_get(msg, &errname, &errmsg)) {
ERR("Objects Get error: %s %s", errname, errmsg);
exit(-1);
}
}
static void register_endpoint(void *data)
{
char * object_path = data;
Eldbus_Object *object = eldbus_object_get(conn, BLUEZ_SERVICE, object_path);
Eldbus_Proxy *adapter = eldbus_proxy_get(object, MEDIA);
Eldbus_Message *msg = eldbus_proxy_method_call_new(adapter, "RegisterEndpoint");
Eldbus_Message_Iter *args = eldbus_message_iter_get(msg);
Eldbus_Message_Iter *dict, *var, *array;
Eldbus_Pending *pending;
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(args, "o{sv}", OWN_OBJECT, &dict);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(dict, "sv", "Capabilities", &var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(var, "ay", &array);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(array, "yyyy", 255, 255, 2, 250);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_container_close(var,array);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_container_close(dict,var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(dict, "sv", "Codec", &var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(var, "y", 0);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_container_close(dict,var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(dict, "sv", "UUID", &var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_arguments_append(var, "s", "0000110A-0000-1000-8000-00805F9B34FB");
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_container_close(dict,var);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
eldbus_message_iter_container_close(args,dict);
NTC("RegisterEndpoint %s", eldbus_message_signature_get(msg));
/* Calling this directly or as job breaks event loop :s */
/* The other end probably never replies since we did not export the object sent as argument but still ... */
pending = eldbus_proxy_send(adapter, msg, on_endpoint_register, NULL, -1);
if (!pending) {
ERR("Failed to call RegisterEndpoint");
exit(-1);
}
/* It seems unreferencing the message here causes problems
eldbus_message_unref(msg); */
free(object_path);
}
static void on_interface_iter(void * data, const void * key, Eldbus_Message_Iter *properties)
{
const char * object = data;
const char * interface = key;
Ecore_Job * job;
if (!strcmp(ADAPTER, interface)) {
NTC(" +%s %s", object, interface);
interfaces = eina_list_append(interfaces, strdup(object));
job = ecore_job_add(register_endpoint, strdup(object));
if (!job) {
ERR("Unable to register main loop job");
}
}
}
static void on_object_iter(void * data EINA_UNUSED, const void * key, Eldbus_Message_Iter *interfaces)
{
const char * object = key;
char *isig, *sig;
NTC(" +%s", object);
sig = eldbus_message_iter_signature_get(interfaces);
isig = strndup(sig + 1, strlen(sig) - 2);
eldbus_message_iter_dict_iterate(interfaces, isig, on_interface_iter, object);
free(isig);
free(sig);
}
static void on_objects_get(void *data EINA_UNUSED, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
{
Eldbus_Message_Iter *arguments, *objects;
const char *errname, *errmsg, *sig = eldbus_message_signature_get(msg);
char * isig;
NTC("Objects Get %s", sig);
if (eldbus_message_error_get(msg, &errname, &errmsg)) {
ERR("Objects Get error: %s %s", errname, errmsg);
exit(-1);
}
arguments = eldbus_message_iter_get(msg);
eldbus_message_iter_arguments_get(arguments, sig, &objects);
isig = strndup(sig + 2, strlen(sig) - 3);
eldbus_message_iter_dict_iterate(objects, isig, on_object_iter, NULL);
free(isig);
}
static void on_interfaces_removed(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
const char * sig = eldbus_message_signature_get(msg);
const char * object, *interface;
Eldbus_Message_Iter *arguments, *i_ifaces;
NTC("Iface Removed %s", sig);
arguments = eldbus_message_iter_get(msg);
eldbus_message_iter_arguments_get(arguments, sig, &object, &i_ifaces);
NTC("-%s", object);
while (eldbus_message_iter_get_and_next(i_ifaces, 's', &interface)) {
if (!strcmp(ADAPTER, interface)) {
NTC(" -%s %s", object, interface);
interfaces = eina_list_remove(interfaces, object);
}
}
}
static void on_interfaces_added(void *data EINA_UNUSED, const Eldbus_Message *msg)
{
const char * sig = eldbus_message_signature_get(msg);
const char * object;
Eldbus_Message_Iter *arguments, *interfaces;
NTC("Iface Added %s", sig);
arguments = eldbus_message_iter_get(msg);
eldbus_message_iter_arguments_get(arguments, sig, &object, &interfaces);
on_object_iter(NULL, object, interfaces);
}
static void on_service_registered(void *data, const Eldbus_Message * msg, Eldbus_Pending * pending)
{
const char *errname, *errmsg, *sig = eldbus_message_signature_get(msg);
Eldbus_Proxy *manager = data;
NTC("service registered %s", sig);
if (eldbus_message_error_get(msg, &errname, &errmsg)) {
ERR("service registration error: %s %s", errname, errmsg);
exit(-1);
}
pending = eldbus_proxy_call(manager, "GetManagedObjects", on_objects_get, NULL, -1, "");
if (!pending) {
ERR("Failed to call GetManagedObjects");
exit(-1);
}
}
static Eldbus_Message * on_ep_set_configuration(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message * msg)
{
return NULL;
}
static Eldbus_Message * on_ep_select_configuration(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message * msg)
{
return NULL;
}
static Eldbus_Message * on_ep_clear_configuration(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message * msg)
{
return NULL;
}
static Eldbus_Message * on_ep_release(const Eldbus_Service_Interface *iface EINA_UNUSED, const Eldbus_Message * msg)
{
return NULL;
}
static void register_service(void *data)
{
Eldbus_Proxy *manager = data;
Eldbus_Pending *pending;
const Eldbus_Method media_methods[] = {
{ "SetConfiguration",
ELDBUS_ARGS({"o", "transport"},{"{sv}", "properties"}), NULL,
on_ep_set_configuration, 0 },
{ "SelectConfiguration",
ELDBUS_ARGS({"ay", "capabilities"}),
ELDBUS_ARGS({"ay", "capabilities"}),
on_ep_select_configuration, 0 },
{ "ClearConfiguration",
ELDBUS_ARGS({"o", "transport"}), NULL,
on_ep_clear_configuration, 0 },
{ "Release", NULL, NULL, on_ep_release, 0 },
{ NULL, NULL, NULL, NULL, 0 }
};
const Eldbus_Service_Interface_Desc media = {
MEDIA,
media_methods,
NULL,
NULL,
NULL,
NULL
};
NTC("Register Service");
srv = eldbus_service_interface_register(conn, OWN_OBJECT, &media);
pending = eldbus_name_request(conn, OWN_SERVICE, ELDBUS_NAME_REQUEST_FLAG_DO_NOT_QUEUE, on_service_registered, manager);
if (!pending) {
ERR("Failed to call name request");
exit(-1);
}
}
static Eldbus_Signal_Handler*
chk_eldbus_proxy_signal_handler_add ( Eldbus_Proxy * proxy,
const char * member,
Eldbus_Signal_Cb cb,
const void * cb_data
)
{
Eldbus_Signal_Handler* hnd = eldbus_proxy_signal_handler_add (
proxy,
member,
cb,
cb_data
);
if (!hnd) {
ERR("Failed to set up callback for signal %s", member);
exit(-1);
}
return hnd;
}
int main(int argc, char * argv[])
{
Eldbus_Object *objm;
Eldbus_Proxy *manager;
Ecore_Job * job;
eina_init();
log_dom = eina_log_domain_register(LOGNAME, LOGCOLOR);
if (log_dom < 0) {
ERR("Unable to create '" LOGNAME "' log domain");
log_dom = EINA_LOG_DOMAIN_GLOBAL;
}
eina_log_level_set(EINA_LOG_LEVEL_INFO);
ecore_init();
eldbus_init();
conn = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
if (!conn){
ERR("Unable to connect to system bus");
return EXIT_FAILURE;
}
objm = eldbus_object_get(conn, BLUEZ_SERVICE, "/");
if (!objm){
ERR("Unable to get " BLUEZ_SERVICE " manager");
return EXIT_FAILURE;
}
manager = eldbus_proxy_get(objm, MANAGER);
if (!manager) {
ERR("Unable to get " BLUEZ_SERVICE " manager proxy");
return EXIT_FAILURE;
}
job = ecore_job_add(register_service, manager);
if (!job) {
ERR("Unable to register main loop job");
return EXIT_FAILURE;
}
chk_eldbus_proxy_signal_handler_add(manager, "InterfacesAdded", on_interfaces_added, NULL);
chk_eldbus_proxy_signal_handler_add(manager, "InterfacesRemoved", on_interfaces_removed, NULL);
ecore_main_loop_begin();
if (timer)
ecore_timer_del(timer);
eldbus_proxy_unref(manager);
eldbus_object_unref(objm);
eldbus_connection_unref(conn);
eldbus_shutdown();
ecore_shutdown();
eina_log_domain_unregister(log_dom);
eina_shutdown();
return 0;
}
require 'dbus'
def q *args
# p *args
end
class Exception
def pp
STDERR.puts self.inspect
self.backtrace.each{|l|
STDERR.puts " " + l
}
end
end
class Endpoint < DBus::Object
# byte array for SBC capabilities is passed around as native integer in libsbc ..
def bytes2num arr
arr.pack("C4").unpack("I")
end
def num2bytes num
num.pack("I").unpack("C4")
end
def max i,j
return i if i > j
return j
end
def min i,j
return i if i < j
return j
end
CSIE = [
[
[:mode, :JOINT_STEREO],
[:mode, :STEREO],
[:mode, :DUAL_CHANNEL],
[:mode, :MONO],
[:frequency, 48000],
[:frequency, 44100],
[:frequency, 32000],
[:frequency, 16000],
],[
[:method, :Loudness],
[:method, :SNR],
[:subbands, 8],
[:subbands, 4],
[:block_length, 16],
[:block_length, 12],
[:block_length, 8],
[:block_length, 4],
]
]
CSIE_prio = {
:mode => [ :STEREO, :DUAL_CHANNEL, :JOINT_STEREO, :MONO],
:frequency => [ 48000, 44100, 32000, 16000],
:method => [ :SNR, :Loudness],
:subbands => [ 8, 4],
:block_length => [ 16, 12, 8, 4],
}
Codec = 0 #sbc
CodecCaps = [255, 255, 2, 250] #everything
TransportIface = 'org.bluez.MediaTransport1'
def empty_sbc_caps caps
res = {
:frequency => [],
:mode => [],
:block_length => [],
:subbands => [],
:method => [],
}
if caps.kind_of? Hash
res[:min_bitpool] = caps[:min_bitpool]
res[:max_bitpool] = caps[:max_bitpool]
else
caps = normalize_sbc_caps caps
res[:min_bitpool] = caps[2]
res[:max_bitpool] = caps[3]
end
res
end
def decode_sbc_caps caps
#Caps is a byte array. Spec notion appears to be little-endian within byte
caps = caps
caps = num2bytes caps if caps.kind_of? Numeric
res = empty_sbc_caps caps
(0..1).each{|i|
(0...8).each{|j|
res[CSIE[i][j][0]] << CSIE[i][j][1] if caps[i] & (1 << j) != 0
}
}
res
end
def encode_sbc_caps caps
res = [0, 0, caps[:min_bitpool], caps[:max_bitpool]]
(0..1).each{|i|
(0...8).each{|j|
res[i] |= (1 << j) if caps[CSIE[i][j][0]].include? CSIE[i][j][1]
}
}
res
end
def normalize_sbc_caps caps, type = Array
raise unless [Array, Hash, Numeric].include? type
return caps if type == Hash and caps.kind_of? Hash
return caps if type == Numeric and caps.kind_of? Numeric
return num2bytes bytes2num caps if type == Array and caps.kind_of? Array
return normalize_sbc_caps (encode_sbc_caps caps), type if caps.kind_of? Hash
return normalize_sbc_caps (num2bytes caps), type if caps.kind_of? Numeric
return decode_sbc_caps caps if type == Hash
return bytes2num caps if type == Numeric
raise
end
def intersect_sbc_caps caps1, caps2
caps1 = caps1
caps2 = caps2
type = Array
type = Hash if caps1.kind_of? Hash and caps2.kind_of? Hash
type = Numeric if caps1.kind_of? Numeric and caps2.kind_of? Numeric
caps1 = normalize_sbc_caps caps1
caps2 = normalize_sbc_caps caps2
normalize_sbc_caps [
caps1[0] & caps2[0],
caps1[1] & caps2[1],
max(caps1[2], caps2[2]),
min(caps1[3], caps2[3])
], type
end
def pick_sbc_caps caps
caps = normalize_sbc_caps caps, Hash
res = empty_sbc_caps caps
CSIE_prio.keys.each{|k|
CSIE_prio[k].each{|v|
if caps[k].include? v
res[k] = [v]
break
end
}
}
normalize_sbc_caps res
end
# This is the whole MediaEndpoint interface. I did not find an example
# that specifies return value. It should be obvious either way
dbus_interface "org.bluez.MediaEndpoint1" do
dbus_method :SetConfiguration, "in transport:o, in props:d" do |transport, props|
begin
p :SetConfiguration, transport, props
@thread = Thread.new {
Thread.stop
loop {
begin
fd, mtur, mtuw = @transport.TryAcquire
STDERR.puts "Transport fd acquired #{fd}, #{mtur}, #{mtuw}"
Thread.exit
rescue DBus::Error
p $!.inspect
Thread.exit if $!.name != 'org.bluez.Error.NotAvailable'
end
sleep 1
}}
@transport = $service.object transport
@transport.introspect # cannot set default interface otherwise :s
# cannot even acces interfaces directly without introspect :/
@transport.default_iface = TransportIface
# properties are only available on an iface
# cannot read them from object directly
iface = @transport[TransportIface]
begin
dev = iface['Device']
p dev
vol = iface['Volume']
p vol
rescue Exception
p $!.inspect
end
@thread.wakeup
nil
rescue Exception
$!.pp
raise
end
end
dbus_method :SelectConfiguration, "in caps:ay, out caps:ay" do |caps|
begin
p :SelectConfiguration, (decode_sbc_caps caps)
caps = intersect_sbc_caps CodecCaps, caps
q decode_sbc_caps caps
caps = pick_sbc_caps caps
p decode_sbc_caps caps
q caps
# The format of dictionary values and return values differs :/
# Actually, it seems this is correct since the
# dictionary value is variant and so needs type
# information but the array values are uniform
# and so donot need type information and so
# type information should not be returned.
[caps]
rescue Exception
$!.pp
raise
end
end
dbus_method :ClearConfiguration, "in transport:o" do |transport|
begin
p :ClearConfiguration, transport
nil
rescue Exception
$!.pp
raise
end
end
dbus_method :Release do
begin
p :Release
nil
rescue Exception
$1.pp
raise
end
end
end
end
class BTTest
def initialize
$bus = DBus::SystemBus.instance
$service_path = %w(org bluez)
$service = $bus.service $service_path.join('.')
$service.introspect # othewise cannot walk dbus namespace
prefix = $service.root
$service_path.each{|e|
prefix = prefix[e]
}
@media_iface = ($service_path.clone << 'Media1').join '.'
@own_path = %w(test testenkrumppel) << "u" + Process::Sys.getuid.to_s
@own_service = $bus.request_service @own_path.join('.')
@interfaces = Hash.new
# This is where interfaces seem to be visible.
# Not sure if there is specification for this.
prefix.keys.each{|i|
@interfaces[i] = $service.object ("/" + ($service_path.clone << i).join('/'))
@interfaces[i].default_iface = @media_iface
}
@interfaces.each{|k,i|
e = Endpoint.new "/" + (@own_path.clone << k).join("/")
p e.path
@own_service.export e
props = {
'UUID' => '0000110A-0000-1000-8000-00805F9B34FB',
'Codec' => DBus.variant("y", Endpoint::Codec),
'Capabilities' => DBus.variant("ay", Endpoint::CodecCaps)
}
p props
r = i.RegisterEndpoint e.path, props
q Endpoint::CodecCaps
q e.decode_sbc_caps Endpoint::CodecCaps
q e.encode_sbc_caps(e.decode_sbc_caps Endpoint::CodecCaps)
q (e.intersect_sbc_caps Endpoint::CodecCaps, [255,255,2,53])
q e.decode_sbc_caps(e.intersect_sbc_caps Endpoint::CodecCaps, [255,255,2,53])
q e.pick_sbc_caps(e.intersect_sbc_caps Endpoint::CodecCaps, [255,255,2,53])
q e.decode_sbc_caps e.pick_sbc_caps(e.intersect_sbc_caps Endpoint::CodecCaps, [255,255,2,53])
}
loop = DBus::Main.new
loop << DBus::SystemBus.instance
loop.run
end
end
BTTest.new
CFLAGS=-Wall $(shell pkg-config --cflags eldbus ecore eina)
LDFLAGS=$(shell pkg-config --libs eldbus ecore eina)
all: bttest
./bttest
.PHONY: all
<?xml version="1.0"?><!--*-nxml-*-->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!--
Test config
-->
<busconfig>
<policy group="bluetooth">
<allow own_prefix="test.testenkrumppel"/>
<allow own="test.testenkrumppel"/>
</policy>
</busconfig>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment