/* * Copyright (C) 2014 Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb" /* ---------------------------------------------------------------------------------------------------- */ static GDBusNodeInfo *introspection_data = NULL; /* Introspection data for the service we are exporting */ static const gchar introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; typedef struct { GDBusConnection *conn; GDBusProxy *manager; gchar *uuid; gchar *name; guint id; GHashTable *objs; } Profile; typedef struct { Profile *profile; gchar *obj; gint fd; guint id; gchar *state; gchar *owner; gchar *spath; gchar *src_addr; gchar *dst_addr; GVariantIter *props; gchar *name; GIOChannel *channel; } MediaTransport; static void send_set_configuration_reply (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GDBusConnection *connection = G_DBUS_CONNECTION (source_object); g_dbus_connection_call_finish (connection, res, &error); if (error) { g_printerr ("error doing SetConfiguration %s\n", error->message); g_clear_error (&error); } } static void send_set_configuration (MediaTransport *t) { /* send transport to endpoint */ GVariantBuilder *b; b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (b, "{sv}", "UUID", g_variant_new_string ("0000111f-0000-1000-8000-00805f9b34fb")); g_variant_builder_add (b, "{sv}", "Device", g_variant_new_object_path (t->obj)); g_dbus_connection_call (t->profile->conn, t->owner, t->spath, "org.bluez.MediaEndpoint1", "SetConfiguration", g_variant_new ("(oa{sv})", t->name, b), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, send_set_configuration_reply, t); } static void send_clear_configuration_reply (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *error = NULL; GDBusConnection *connection = G_DBUS_CONNECTION (source_object); g_dbus_connection_call_finish (connection, res, &error); if (error) { g_printerr ("error doing ClearConfiguration %s\n", error->message); g_clear_error (&error); } } static void send_clear_configuration (MediaTransport *t) { /* send transport to endpoint */ g_dbus_connection_call (t->profile->conn, t->owner, t->spath, "org.bluez.MediaEndpoint1", "ClearConfiguration", g_variant_new ("(o)", t->name), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, send_clear_configuration_reply, t); } static void media_transport_configure_endpoint (MediaTransport *t) { GVariantIter *props_i = t->props; GVariant *value; gchar *key; /* now go over all registered endpoints and inform them about the * new transport object */ while (g_variant_iter_next (props_i, "{&sv}", &key, &value)) { g_print (" prop: %s\n", key); if (g_str_equal (key, "MediaEndpoints")) { GVariantIter *endpoints_i; gchar *owner; GVariant *oprops; g_variant_get (value, "a{sv}", &endpoints_i); while (g_variant_iter_next (endpoints_i, "{sv}", &owner, &oprops)) { GVariantIter *oprops_i; gchar *pname; const gchar *spath; GVariant *pval; g_print (" owner: %s\n", owner); g_variant_get (oprops, "a{sv}", &oprops_i); while (g_variant_iter_next (oprops_i, "{&sv}", &pname, &pval)) { if (g_variant_is_of_type (pval, G_VARIANT_TYPE_OBJECT_PATH)) { t->spath = g_variant_dup_string (pval, NULL); g_print (" key: %s object-path=%s\n", pname, spath); } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_BYTE)) { g_print (" key: %s byte=%c\n", pname, g_variant_get_byte (pval)); } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_ARRAY)) { g_print (" key: %s array\n", pname); } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_STRING)) { g_print (" key: %s string=%s\n", pname, g_variant_get_string (pval, NULL)); } else { g_print (" key: %s (%s)\n", pname, g_variant_get_type_string (pval)); } g_variant_unref (pval); } t->owner = owner; send_set_configuration (t); g_variant_iter_free (oprops_i); g_variant_unref (oprops); } g_variant_iter_free (endpoints_i); } g_variant_unref (value); } g_variant_iter_free (props_i); } static gboolean rfcomm_io_cb (GIOChannel *source, GIOCondition condition, gpointer data) { gchar buf[512]; GIOStatus st; MediaTransport *t = data; g_print ("condition %d\n", condition); if (condition & (G_IO_ERR | G_IO_HUP)) return FALSE; if (condition & G_IO_IN) { gint fd = g_io_channel_unix_get_fd (source); gsize length; g_print ("we can read\n", condition); g_io_channel_read_chars (source, buf, 512, &length, NULL); buf[length] = 0; g_print ("%d %s\n", length, buf); if (g_str_has_prefix (buf, "AT+BRSF=")) { write (fd, "\r\n+BRSF:254\r\n", 13); write (fd, "\r\nOK\r\n", 5); } else if (g_str_has_prefix (buf, "AT+CIND=?")) { write (fd, "\r\n+CIND:254\r\n", 13); write (fd, "\r\nOK\r\n", 5); } else if (g_str_has_prefix (buf, "AT+CIND?")) { write (fd, "\r\n+CIND:254\r\n", 13); write (fd, "\r\nOK\r\n", 5); } else if (g_str_has_prefix (buf, "AT+CMER=")) { write (fd, "\r\n+VGM:15\r\n", 11); write (fd, "\r\n+VGS:15\r\n", 11); write (fd, "\r\nOK\r\n", 5); } else if (g_str_has_prefix (buf, "AT+CMEE=")) { write (fd, "\r\nOK\r\n", 5); media_transport_configure_endpoint (t); } else { write (fd, "\r\nOK\r\n", 5); } } return TRUE; } static void transport_set_state (MediaTransport *t, const gchar *state) { GVariantBuilder *builder; GVariantBuilder *invalidated_builder; GError *error; error = NULL; builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); g_variant_builder_add (builder, "{sv}", "State", g_variant_new_string (state)); g_dbus_connection_emit_signal (t->profile->conn, NULL, t->name, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new ("(sa{sv}as)", "org.bluez.MediaTransport1", builder, invalidated_builder), &error); g_assert_no_error (error); } static void transport_acquire (MediaTransport *t, gboolean optional, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { GUnixFDList *fdlist; GDBusMessage *reply; GError *error; guchar *blob; gsize out_size; struct sockaddr_sco addr; int err, i; GIOCondition cond; GIOChannel *io; bdaddr_t src; bdaddr_t dst; int voice = 0x60; socklen_t len; gchar *src_addr; gchar *dst_addr; src_addr = t->src_addr; dst_addr = t->dst_addr; for (i = 5; i >= 0; i--, src_addr += 3) src.b[i] = strtol(src_addr, NULL, 16); for (i = 5; i >= 0; i--, dst_addr += 3) dst.b[i] = strtol(dst_addr, NULL, 16); t->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); if (t->fd < 0) { g_printerr("socket(SEQPACKET, SCO)"); return; } g_print ("got fd %d\n", t->fd); transport_set_state (t, "pending"); fdlist = g_unix_fd_list_new (); g_unix_fd_list_append (fdlist, t->fd, NULL); g_dbus_method_invocation_return_value_with_unix_fd_list ( invocation, g_variant_new ("(hqq)", 0, 48, 48), fdlist); memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, &src); if (bind(t->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("bind()"); return; } if (voice) { struct bt_voice opts; /* SCO voice setting */ memset(&opts, 0, sizeof(opts)); opts.setting = voice; if (setsockopt(t->fd, SOL_BLUETOOTH, BT_VOICE, &opts, sizeof(opts)) < 0) { perror("setsockopt()"); return; } } memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, &dst); g_printerr ("doing connect\n"); err = connect(t->fd, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { perror("connect()"); return; } g_printerr ("connected\n"); transport_set_state (t, "active"); } static void transport_release (MediaTransport *t, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { g_print ("close fd %d\n", t->fd); if (t->fd != -1) { shutdown (t->fd, SHUT_RDWR); close (t->fd); } t->fd = -1; g_dbus_method_invocation_return_value (invocation, NULL); transport_set_state (t, "idle"); } static void transport_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { MediaTransport *t = user_data; if (g_strcmp0 (method_name, "Acquire") == 0) { transport_acquire (t, TRUE, connection, parameters, invocation); } else if (g_strcmp0 (method_name, "TryAcquire") == 0) { transport_acquire (t, FALSE, connection, parameters, invocation); } else if (g_strcmp0 (method_name, "Release") == 0) { transport_release (t, connection, parameters, invocation); } } static GVariant * transport_get_property (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { MediaTransport *t = user_data; GVariant *ret; ret = NULL; if (g_strcmp0 (property_name, "State") == 0) { ret = g_variant_new_string (t->state); } return ret; } static const GDBusInterfaceVTable transport_interface_vtable = { transport_method_call, transport_get_property, NULL }; static MediaTransport * media_transport_free (MediaTransport *t) { g_print ("media transport free %p\n", t); send_clear_configuration (t); if (t->id) g_dbus_connection_unregister_object (t->profile->conn, t->id); if (t->channel) g_io_channel_unref (t->channel); g_free (t->owner); g_free (t->spath); g_free (t->src_addr); g_free (t->dst_addr); g_free (t->obj); g_free (t); } static MediaTransport * media_transport_new (Profile *p, const gchar *obj, gint fd, GVariantIter *props) { MediaTransport *t; GError *error = NULL; GVariant *res, *var; const gchar *adapter; t = g_new0 (MediaTransport, 1); t->profile = p; t->obj = g_strdup (obj); t->fd = fd; t->props = props; t->state = "idle"; g_print ("new media transport %p\n", t); /* make a new transport object to handle the setup of the SCO connection */ t->name = g_strdup_printf ("%s/fd%d", obj, fd); t->id = g_dbus_connection_register_object (p->conn, t->name, introspection_data->interfaces[1], &transport_interface_vtable, t, NULL, /* user_data_free_func */ &error); /* GError** */ if (t->id == 0) { g_printerr ("error registering object %s\n", error->message); media_transport_free (t); return NULL; } res = g_dbus_connection_call_sync (p->conn, "org.bluez", obj, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", "org.bluez.Device1", "Address"), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (res == NULL) { g_printerr ("Can't get Device Address", error->message); return NULL; } g_variant_get (res, "(v)", &var); t->dst_addr = g_variant_dup_string (var, NULL); g_variant_unref (var); g_print ("device addres %s\n", t->dst_addr); res = g_dbus_connection_call_sync (p->conn, "org.bluez", obj, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", "org.bluez.Device1", "Adapter"), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (res == NULL) { g_printerr ("Can't get Adapter", error->message); return NULL; } g_variant_get (res, "(v)", &var); adapter = g_variant_get_string (var, NULL); g_print ("device adapter %s\n", adapter); res = g_dbus_connection_call_sync (p->conn, "org.bluez", adapter, "org.freedesktop.DBus.Properties", "Get", g_variant_new ("(ss)", "org.bluez.Adapter1", "Address"), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); g_variant_unref (var); if (res == NULL) { g_printerr ("Can't get Adapter Address", error->message); return NULL; } g_variant_get (res, "(v)", &var); t->src_addr = g_variant_dup_string (var, NULL); g_variant_unref (var); g_print ("adapter addres %s\n", t->src_addr); t->channel = g_io_channel_unix_new (fd); g_io_channel_set_close_on_unref (t->channel, TRUE); g_io_add_watch (t->channel, G_IO_IN | G_IO_ERR | G_IO_HUP, rfcomm_io_cb, t); g_hash_table_insert (p->objs, (gpointer) obj, t); return t; } static void profile_new_connection (Profile *p, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { gchar *obj; gint32 fd; GVariantIter *props; GDBusMessage *message; GUnixFDList * fdlist; MediaTransport *t; g_variant_get (parameters, "(oha{sv})", &obj, &fd, &props); message = g_dbus_method_invocation_get_message (invocation); fdlist = g_dbus_message_get_unix_fd_list (message); fd = g_unix_fd_list_get (fdlist, fd, NULL); g_print ("NewConnection %s %d\n", obj, fd); g_dbus_method_invocation_return_value (invocation, NULL); /* make a new transport object to handle the setup of the SCO connection */ t = media_transport_new (p, obj, fd, props); } static void profile_request_disconnection (Profile *p, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { gchar *obj; MediaTransport *t; g_variant_get (parameters, "(o)", &obj); g_print ("RequestDisconnection %s\n", obj); t = g_hash_table_lookup (p->objs, obj); if (t == NULL) { g_warning ("unknown transport object %s", obj); goto done; } g_hash_table_remove (p->objs, obj); media_transport_free (t); done: g_dbus_method_invocation_return_value (invocation, NULL); } static void profile_release (Profile *p, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { g_dbus_method_invocation_return_value (invocation, NULL); } static void profile_cancel (Profile *p, GDBusConnection *connection, GVariant *parameters, GDBusMethodInvocation *invocation) { g_dbus_method_invocation_return_value (invocation, NULL); } static void profile_method_call (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { Profile *p = user_data; if (g_strcmp0 (method_name, "Release") == 0) { profile_release (p, connection, parameters, invocation); } else if (g_strcmp0 (method_name, "Cancel") == 0) { profile_cancel (p, connection, parameters, invocation); } else if (g_strcmp0 (method_name, "RequestDisconnection") == 0) { profile_request_disconnection (p, connection, parameters, invocation); } else if (g_strcmp0 (method_name, "NewConnection") == 0) { profile_new_connection (p, connection, parameters, invocation); } else g_print ("Unhandled %s\n", method_name); } static const GDBusInterfaceVTable profile_interface_vtable = { profile_method_call, NULL, NULL }; static void profile_free (Profile *p) { if (p->id) g_dbus_connection_unregister_object (p->conn, p->id); g_free (p->uuid); g_free (p->name); g_hash_table_unref (p->objs); g_free (p); } static Profile * register_profile (GDBusConnection *connection, GDBusProxy * manager, const gchar *uuid) { GError *error = NULL; guint registration_id; Profile *profile; GVariant *res; profile = g_new0 (Profile, 1); profile->conn = connection; profile->manager = manager; profile->name = g_strdup_printf ("/org/test/Profile"); profile->uuid = g_strdup (uuid); profile->objs = g_hash_table_new (g_str_hash, g_str_equal); profile->id = g_dbus_connection_register_object (connection, profile->name, introspection_data->interfaces[0], &profile_interface_vtable, profile, NULL, /* user_data_free_func */ &error); /* GError** */ if (profile->id == 0) { g_printerr ("error registering object %s\n", error->message); profile_free (profile); return NULL; } g_print ("Register profile\n"); res = g_dbus_proxy_call_sync (manager, "RegisterProfile", g_variant_new ("(osa{sv})", profile->name, uuid, NULL), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (res == NULL) { g_printerr ("error registering %s\n", error->message); profile_free (profile); return NULL; } g_print ("Profile registered\n"); g_variant_unref (res); return profile; } int main (int argc, char *argv[]) { GDBusConnection * connection; GError *error = NULL; GMainLoop *loop; guint registration_id; GDBusProxy *manager; g_print ("connecting to system bus\n"); connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (error != NULL) { g_printerr ("error getting bus: %s", error->message); return -1; } introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); g_assert (introspection_data != NULL); g_print ("making proxy for ProfileManager1\n"); manager = g_dbus_proxy_new_sync (connection, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.bluez", "/org/bluez", "org.bluez.ProfileManager1", NULL, &error); if (error != NULL) { g_printerr ("error getting ProfileManager1: %s", error->message); return -1; } if (register_profile (connection, manager, HFP_AG_UUID) == NULL) return -1; g_print ("going into mainloop\n"); loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); g_print ("exit mainloop\n"); g_dbus_node_info_unref (introspection_data); return 0; }