Last active
December 1, 2015 10:12
-
-
Save hramrach/606ae05725f49eeaabf3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CFLAGS=-Wall $(shell pkg-config --cflags eldbus ecore eina) | |
LDFLAGS=$(shell pkg-config --libs eldbus ecore eina) | |
all: bttest | |
./bttest | |
.PHONY: all |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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