I am trying to write some code, which can scan for nearby bluetooth devices.
I think it is a bit complicated to understand, so i am asking for help.
To start with I will explain my scenario. I know from the bluez adapter API (https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt), that i can call a method on dbus, which is taking no parameters to start scanning for devices. that method is called "StartDiscovery". I dont know if it would be a proper way to this asynchronously? And im also unsure about the function
g_main_loop_run()
I need to use it for the method to keep scanning for devices, but i dont know what the proper way is to stop the loop again.
Here is my code, assume that a GDBusProxy is obtained
start_discover_variant = g_dbus_proxy_call_sync(proxy,"StartDiscovery", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
if (start_discover_variant == NULL)
{
syslog(LOG_ERR, "%s", error->message);
g_error_free(error);
g_assert_no_error(error);
}
sleep(20);
stop_discover_variant = g_dbus_proxy_call_sync(proxy,"StopDiscovery", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
if (stop_discover_variant == NULL)
{
syslog(LOG_ERR, "%s", error->message);
g_error_free(error);
g_assert_no_error(error);
}
g_variant_unref(stop_discover_variant);
As you can see i implemented a 20 second sleep, but since it can run asynchronously, maybe some kind of timeout signal can be used instead, or is the sleep function ok?
From your sample code I understand that you stopping the scanning after 20 seconds, which is not asynchronous. Asynchronous means you need to take decision based on external or internal events. This can be POSIX signal/timer (using timer_create) or any events.
If you are sure about using timed/timeout based stop, better use "timer_create" in Linux or any other timer based operations. If you want to stop scanning based on scanned devices, then you can use callback for "StartDiscovery" results.
Here is the complete example of listing the devices after scanning using GDBUS (but not proxy). The same example can be extended using proxy.
/*
* bluez_adapter_scan.c - Scan for bluetooth devices
* - This example scans for new devices after powering the adapter, if any devices
* appeared in /org/hciX/dev_XX_YY_ZZ_AA_BB_CC, it is monitered using "InterfaceAdded"
* signal and all the properties of the device is printed
* - Scanning continues to run until any device is disappered, this happens after 180 seconds
* automatically if the device is not used.
* gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/bluez_adapter_scan ./bluez_adapter_scan.c `pkg-config --libs glib-2.0 gio-2.0`
*/
#include <glib.h>
#include <gio/gio.h>
GDBusConnection *con;
static void bluez_property_value(const gchar *key, GVariant *value)
{
const gchar *type = g_variant_get_type_string(value);
g_print("\t%s : ", key);
switch(*type) {
case 'o':
case 's':
g_print("%s\n", g_variant_get_string(value, NULL));
break;
case 'b':
g_print("%d\n", g_variant_get_boolean(value));
break;
case 'u':
g_print("%d\n", g_variant_get_uint32(value));
break;
case 'a':
/* TODO Handling only 'as', but not array of dicts */
if(g_strcmp0(type, "as"))
break;
g_print("\n");
const gchar *uuid;
GVariantIter i;
g_variant_iter_init(&i, value);
while(g_variant_iter_next(&i, "s", &uuid))
g_print("\t\t%s\n", uuid);
break;
default:
g_print("Other\n");
break;
}
}
static void bluez_device_appeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
(void)user_data;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
GVariant *properties;
g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces);
while(g_variant_iter_next(interfaces, "{&s#a{sv}}", &interface_name, &properties)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
g_print("[ %s ]\n", object);
const gchar *property_name;
GVariantIter i;
GVariant *prop_val;
g_variant_iter_init(&i, properties);
while(g_variant_iter_next(&i, "{&sv}", &property_name, &prop_val))
bluez_property_value(property_name, prop_val);
g_variant_unref(prop_val);
}
g_variant_unref(properties);
}
return;
}
#define BT_ADDRESS_STRING_SIZE 18
static void bluez_device_disappeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
char address[BT_ADDRESS_STRING_SIZE] = {'\0'};
g_variant_get(parameters, "(&oas)", &object, &interfaces);
while(g_variant_iter_next(interfaces, "s", &interface_name)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
int i;
char *tmp = g_strstr_len(object, -1, "dev_") + 4;
for(i = 0; *tmp != '\0'; i++, tmp++) {
if(*tmp == '_') {
address[i] = ':';
continue;
}
address[i] = *tmp;
}
g_print("\nDevice %s removed\n", address);
g_main_loop_quit((GMainLoop *)user_data);
}
}
return;
}
static void bluez_signal_adapter_changed(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *signal,
GVariant *params,
void *userdata)
{
(void)conn;
(void)sender;
(void)path;
(void)interface;
(void)userdata;
GVariantIter *properties = NULL;
GVariantIter *unknown = NULL;
const char *iface;
const char *key;
GVariant *value = NULL;
const gchar *signature = g_variant_get_type_string(params);
if(g_strcmp0(signature, "(sa{sv}as)") != 0) {
g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)");
goto done;
}
g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown);
while(g_variant_iter_next(properties, "{&sv}", &key, &value)) {
if(!g_strcmp0(key, "Powered")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter is Powered \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
if(!g_strcmp0(key, "Discovering")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter scan \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
}
done:
if(properties != NULL)
g_variant_iter_free(properties);
if(value != NULL)
g_variant_unref(value);
}
static int bluez_adapter_call_method(const char *method)
{
GVariant *result;
GError *error = NULL;
result = g_dbus_connection_call_sync(con,
"org.bluez",
/* TODO Find the adapter path runtime */
"/org/bluez/hci0",
"org.bluez.Adapter1",
method,
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_variant_unref(result);
return 0;
}
static int bluez_adapter_set_property(const char *prop, GVariant *value)
{
GVariant *result;
GError *error = NULL;
result = g_dbus_connection_call_sync(con,
"org.bluez",
"/org/bluez/hci0",
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new("(ssv)", "org.bluez.Adapter1", prop, value),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_variant_unref(result);
return 0;
}
int main(void)
{
GMainLoop *loop;
int rc;
guint prop_changed;
guint iface_added;
guint iface_removed;
con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
if(con == NULL) {
g_print("Not able to get connection to system bus\n");
return 1;
}
loop = g_main_loop_new(NULL, FALSE);
prop_changed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
NULL,
"org.bluez.Adapter1",
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_signal_adapter_changed,
NULL,
NULL);
iface_added = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_appeared,
loop,
NULL);
iface_removed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_disappeared,
loop,
NULL);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", TRUE));
if(rc) {
g_print("Not able to enable the adapter\n");
goto fail;
}
rc = bluez_adapter_call_method("StartDiscovery");
if(rc) {
g_print("Not able to scan for new devices\n");
goto fail;
}
g_main_loop_run(loop);
rc = bluez_adapter_call_method("StopDiscovery");
if(rc)
g_print("Not able to stop scanning\n");
g_usleep(100);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", FALSE));
if(rc)
g_print("Not able to disable the adapter\n");
fail:
g_dbus_connection_signal_unsubscribe(con, prop_changed);
g_dbus_connection_signal_unsubscribe(con, iface_added);
g_dbus_connection_signal_unsubscribe(con, iface_removed);
g_object_unref(con);
return 0;
}
You can find more information about this example here. You can use "bluez_device_appeared" to decide to stop discovery or timer based if no devices appeared for X seconds.
You function can only call "StartDiscovery" and arm the timer using "timer_settime" by having callback in "struct sigevent sev.sigev_notify_function = stop_discovery_cb_handler;"
With this you can avoid sleeping in actual function and leave the callback to stop discovery.
Related
I'm trying to use the GStreamer's appsrc element with rtpbin and udpsink to create an RTP sender using the VP8 codec (vp8enc).
This program creates a fake video stream by switching black and white frames every 100 milliseconds. You can see the video by uncommenting the macro USE_AUTOVIDEOSINK.
When I run the receiver, I only get the first frame. It doesn't blink. What am I doing wrong?
PS: I'm using libgstreamer 1.20.3 from Ubuntu 22.04.
// broadcaster.c
//
// Compile with:
// c99 -Wall -O3 -std=c99 `pkg-config gstreamer-1.0 --cflags --libs-only-l` -O0 -g -c broadcaster.c -o broadcaster
#include <gst/gst.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <stdint.h>
//#define USE_AUTOVIDEOSINK
#define FRAME_WIDTH 320
#define FRAME_HEIGHT 240
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT)
#define FRAME_RATE_INTERVAL_MS 100
#define VIDEO_PT 101
#define VIDEO_SSRC 2222
#define VIDEO_TRANSPORT_HOST "localhost"
#define VIDEO_TRANSPORT_PORT 1234
typedef struct {
GstElement *pipeline;
GstElement *video_appsrc;
GstElement *videoconvert;
#ifdef USE_AUTOVIDEOSINK
GstElement *autovideosink;
#else
GstElement *rtpbin;
GstElement *vp8enc;
GstElement *rtpvp8pay;
GstElement *udpsink;
#endif
gboolean need_video_data;
gboolean playing;
gboolean use_black_buffer;
} CustomData;
static void need_video_data_callback(
GstElement *appsrc,
guint length,
CustomData *data
);
static void enough_video_data_callback(
GstElement *appsrc,
CustomData *data
);
#ifndef USE_AUTOVIDEOSINK
static void pad_added_handler(
GstElement *src,
GstPad *new_pad,
CustomData *data
);
#endif
static void* thread_proc(
void *p_data
);
static int msleep(
long msec
);
static gboolean terminate = FALSE;
static uint16_t black_buffer[FRAME_SIZE];
static uint16_t white_buffer[FRAME_SIZE];
int main(int argc, char *argv[]) {
CustomData data;
GstBus *bus;
GstMessage *msg;
pthread_t thread;
#ifndef USE_AUTOVIDEOSINK
GstPad *rtpvp8pay_src_pad, *rtpbin_send_rtp_sink_0_pad;
#endif
for (int i = 0; i < FRAME_SIZE; ++i) {
black_buffer[i] = 0;
white_buffer[i] = 0xFFFF;
}
gst_init(&argc, &argv);
data.pipeline = gst_pipeline_new("broadcaster-pipeline");
data.video_appsrc = gst_element_factory_make(
"appsrc",
"video_appsrc"
);
data.videoconvert = gst_element_factory_make(
"videoconvert",
"videoconvert"
);
#ifdef USE_AUTOVIDEOSINK
data.autovideosink = gst_element_factory_make(
"autovideosink",
"autovideosink"
);
#else
data.rtpbin = gst_element_factory_make(
"rtpbin",
"rtpbin"
);
data.vp8enc = gst_element_factory_make(
"vp8enc",
"vp8enc"
);
data.rtpvp8pay = gst_element_factory_make(
"rtpvp8pay",
"rtpvp8pay"
);
data.udpsink = gst_element_factory_make(
"udpsink",
"udpsink"
);
#endif
data.need_video_data = FALSE;
data.playing = FALSE;
data.use_black_buffer = FALSE;
if (
!data.pipeline ||
!data.video_appsrc ||
!data.videoconvert ||
#ifdef USE_AUTOVIDEOSINK
!data.autovideosink
#else
!data.rtpbin ||
!data.vp8enc ||
!data.rtpvp8pay ||
!data.udpsink
#endif
) {
g_error("Error: Not all gstreamer elements could be created.\n");
return -1;
}
gst_bin_add_many(
GST_BIN(data.pipeline),
data.video_appsrc,
data.videoconvert,
#ifdef USE_AUTOVIDEOSINK
data.autovideosink,
#else
data.rtpbin,
data.vp8enc,
data.rtpvp8pay,
data.udpsink,
#endif
NULL
);
if (
!gst_element_link_many(
data.video_appsrc,
data.videoconvert,
#ifdef USE_AUTOVIDEOSINK
data.autovideosink,
#else
data.vp8enc,
data.rtpvp8pay,
#endif
NULL
)
) {
g_printerr("Error: Not all video elements could be linked.\n");
gst_object_unref(data.pipeline);
return -1;
}
g_object_set(
data.video_appsrc,
"is-live", TRUE,
"stream-type", 0,
"format", GST_FORMAT_TIME,
NULL
);
#ifndef USE_AUTOVIDEOSINK
g_object_set(
data.vp8enc,
"target-bitrate", 1000000,
"deadline", 1,
"cpu-used", 4,
NULL
);
g_object_set(
data.rtpvp8pay,
"pt", VIDEO_PT,
"ssrc", VIDEO_SSRC,
"picture-id-mode", 2,
NULL
);
g_object_set(
data.udpsink,
"host", VIDEO_TRANSPORT_HOST,
"port", VIDEO_TRANSPORT_PORT,
NULL
);
#endif
g_signal_connect(
data.video_appsrc,
"need-data",
G_CALLBACK(need_video_data_callback),
&data
);
g_signal_connect(
data.video_appsrc,
"enough-data",
G_CALLBACK(enough_video_data_callback),
&data
);
#ifndef USE_AUTOVIDEOSINK
g_signal_connect(
data.rtpbin,
"pad-added",
G_CALLBACK(pad_added_handler),
&data
);
rtpvp8pay_src_pad = gst_element_get_static_pad(
data.rtpvp8pay,
"src"
);
rtpbin_send_rtp_sink_0_pad = gst_element_request_pad_simple(
data.rtpbin,
"send_rtp_sink_%u"
);
if (
gst_pad_link(
rtpvp8pay_src_pad,
rtpbin_send_rtp_sink_0_pad
) != GST_PAD_LINK_OK
) {
g_printerr("Error: rtpvp8pay could not be linked.\n");
gst_object_unref(data.pipeline);
return -1;
}
#endif
if (pthread_create(&thread, NULL, thread_proc, &data)) {
g_printerr("Could not create thread.\n");
return -1;
}
bus = gst_element_get_bus(data.pipeline);
do {
msg = gst_bus_timed_pop_filtered(
bus,
GST_CLOCK_TIME_NONE,
(GstMessageType)(
GST_MESSAGE_STATE_CHANGED |
GST_MESSAGE_ERROR |
GST_MESSAGE_EOS
)
);
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("Error received from element %s: %s\n",
GST_OBJECT_NAME(msg->src), err->message);
g_printerr("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error(&err);
g_free(debug_info);
terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream (EOS) reached.\n");
terminate = TRUE;
break;
case GST_MESSAGE_STATE_CHANGED:
if (GST_MESSAGE_SRC(msg) == GST_OBJECT(data.pipeline)) {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed(
msg,
&old_state,
&new_state,
&pending_state
);
g_print(
"Pipeline state changed from %s to %s: \n",
gst_element_state_get_name(old_state),
gst_element_state_get_name(new_state)
);
if (
g_str_equal(
gst_element_state_get_name(new_state),
"PLAYING"
)
) {
GST_DEBUG_BIN_TO_DOT_FILE(
GST_BIN(data.pipeline),
GST_DEBUG_GRAPH_SHOW_ALL,
"pipeline"
);
}
}
break;
default:
g_printerr("Unexpected message received.\n");
break;
}
gst_message_unref(msg);
}
} while (!terminate);
gst_object_unref(bus);
gst_element_set_state(data.pipeline, GST_STATE_NULL);
gst_object_unref(data.pipeline);
g_print("Goodbye.\n");
return 0;
}
static void need_video_data_callback(
GstElement *appsrc,
guint length,
CustomData *data
) {
g_print(
"Video data is needed on '%s':\n",
GST_ELEMENT_NAME(appsrc)
);
data->need_video_data = TRUE;
}
static void enough_video_data_callback(
GstElement *appsrc,
CustomData *data
) {
g_print(
"Video data is enough on '%s':\n",
GST_ELEMENT_NAME(appsrc)
);
data->need_video_data = FALSE;
}
#ifndef USE_AUTOVIDEOSINK
static void pad_added_handler(
GstElement *src,
GstPad *new_pad,
CustomData *data
) {
GstPad *sink_pad = NULL;
g_print(
"Received new pad '%s'.'%s':\n",
GST_ELEMENT_NAME(src),
GST_PAD_NAME(new_pad)
);
if (src != data->rtpbin) {
return;
}
if (g_str_equal(GST_PAD_NAME(new_pad), "send_rtp_src_0")) {
sink_pad = gst_element_get_static_pad(
data->udpsink,
"sink"
);
}
if (
sink_pad &&
gst_pad_link(
new_pad,
sink_pad
) != GST_PAD_LINK_OK
) {
g_printerr(
"Error: %s.%s could not be linked to %s.\n",
GST_ELEMENT_NAME(src),
GST_PAD_NAME(new_pad),
GST_PAD_NAME(sink_pad)
);
}
if (sink_pad) {
gst_object_unref(sink_pad);
}
}
#endif
static void* thread_proc(
void *p_data
) {
CustomData *data = (CustomData *)p_data;
GstCaps *video_caps = NULL;
g_print("Generating frames...\n");
while (!terminate) {
if (!video_caps) {
video_caps = gst_caps_new_simple(
"video/x-raw",
"format", G_TYPE_STRING, "RGB16",
"width", G_TYPE_INT, FRAME_WIDTH,
"height", G_TYPE_INT, FRAME_HEIGHT,
NULL
);
g_object_set(
data->video_appsrc,
"caps", video_caps,
NULL
);
}
if (video_caps && !data->playing) {
GstStateChangeReturn ret;
ret = gst_element_set_state(
data->pipeline,
GST_STATE_PLAYING
);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Unable to set the pipeline to the playing state.\n");
terminate = TRUE;
continue;
}
else {
data->playing = TRUE;
g_print("Playing the pipeline.\n");
}
}
if (data->playing && data->need_video_data) {
GstFlowReturn ret;
guint size = FRAME_SIZE * 2;
GstBuffer *buffer = gst_buffer_new_memdup(
data->use_black_buffer ? black_buffer : white_buffer,
size
);
g_signal_emit_by_name(
data->video_appsrc,
"push-buffer",
buffer,
&ret
);
gst_buffer_unref(buffer);
data->use_black_buffer = !data->use_black_buffer;
if (ret != GST_FLOW_OK) {
terminate = TRUE;
}
}
msleep(FRAME_RATE_INTERVAL_MS);
}
return NULL;
}
static int msleep(
long msec
) {
struct timespec ts;
int res;
if (msec < 0) {
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
#!/bin/bash
# receiver.sh
HOST=localhost
VIDEO_RTP_PORT=1234
VIDEO_CAPS="application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=(string)VP8"
VIDEO_DEC="rtpvp8depay ! vp8dec"
VIDEO_SINK="videoconvert ! autovideosink"
gst-launch-1.0 \
rtpbin name=rtpbin \
udpsrc caps=$VIDEO_CAPS address=$HOST port=$VIDEO_RTP_PORT \
! rtpbin.recv_rtp_sink_0 rtpbin. \
! $VIDEO_DEC \
! $VIDEO_SINK
I have asked in the gstreamer-devel mailing list, and it turns out my buffers are missing timestamps.
So I fixed the RTP sender by setting the do-timestamp property TRUE for the appsrc.
g_object_set(
data.video_appsrc,
"is-live", TRUE,
"stream-type", 0,
"format", GST_FORMAT_TIME,
"do-timestamp", TRUE,
NULL
);
How can I receive a signal or notification when a connection is created or destroyed through bluez dbus API?
Polling Connected() of all devices under /org/bluez/hci0 works but it is not an efficient way, IMHO.
You need to listen on PropertiesChanged signal on the org.bluez.Device1 interface. I don't have direct example for the device interface, but the template is below,
static void bluez_signal_device_changed(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *signal,
GVariant *params,
void *userdata)
{
(void)conn;
(void)sender;
(void)path;
(void)interface;
(void)userdata;
GVariantIter *properties = NULL;
GVariantIter *unknown = NULL;
const char *iface;
const char *key;
GVariant *value = NULL;
const gchar *signature = g_variant_get_type_string(params);
if(strcmp(signature, "(sa{sv}as)") != 0) {
g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)");
goto done;
}
g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown);
while(g_variant_iter_next(properties, "{&sv}", &key, &value)) {
if(!g_strcmp0(key, "Connected")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Device is \"%s\"\n", g_variant_get_boolean(value) ? "Connected" : "Disconnected");
}
}
done:
if(properties != NULL)
g_variant_iter_free(properties);
if(value != NULL)
g_variant_unref(value);
}
GDBusConnection *con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
prop_changed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
NULL,
"org.bluez.Device1",
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_signal_device_changed,
NULL,
NULL);
With the above sample code, signal handling function bluez_signal_device_changed is called whenever a property is change in the Device` interface.
You can find more examples in https://gist.github.com/parthitce and explanation in https://www.linumiz.com/
I am trying to write an application which searches Bluetooth devices nearby and communicates with them. My application is going to be written in C, and intended to work under Linux.
Are there any tutorial or sample for working with BlueZ via D-Bus in C ?
Purpose of this application is to send data from a file in BLE.
Can you help me please?
You can find the nearby devices using "StartDiscovery" adapter API using DBUS. With the below sample code, you should be able to scan the nearby devices (both BLE and Calssic). But if you want to scan only the BLE devices nearby, you can use "SetDiscoveryFilter" API to set the transport to "le" and start scanning for the BLE devices (check the second example below).
/*
* bluez_adapter_scan.c - Scan for bluetooth devices
* - This example scans for new devices after powering the adapter, if any devices
* appeared in /org/hciX/dev_XX_YY_ZZ_AA_BB_CC, it is monitered using "InterfaceAdded"
* signal and all the properties of the device is printed
* - Scanning continues to run until any device is disappered, this happens after 180 seconds
* automatically if the device is not used.
* gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/bluez_adapter_scan ./bluez_adapter_scan.c `pkg-config --libs glib-2.0 gio-2.0`
*/
#include <glib.h>
#include <gio/gio.h>
GDBusConnection *con;
static void bluez_property_value(const gchar *key, GVariant *value)
{
const gchar *type = g_variant_get_type_string(value);
g_print("\t%s : ", key);
switch(*type) {
case 'o':
case 's':
g_print("%s\n", g_variant_get_string(value, NULL));
break;
case 'b':
g_print("%d\n", g_variant_get_boolean(value));
break;
case 'u':
g_print("%d\n", g_variant_get_uint32(value));
break;
case 'a':
/* TODO Handling only 'as', but not array of dicts */
if(g_strcmp0(type, "as"))
break;
g_print("\n");
const gchar *uuid;
GVariantIter i;
g_variant_iter_init(&i, value);
while(g_variant_iter_next(&i, "s", &uuid))
g_print("\t\t%s\n", uuid);
break;
default:
g_print("Other\n");
break;
}
}
static void bluez_device_appeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
(void)user_data;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
GVariant *properties;
g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces);
while(g_variant_iter_next(interfaces, "{&s#a{sv}}", &interface_name, &properties)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
g_print("[ %s ]\n", object);
const gchar *property_name;
GVariantIter i;
GVariant *prop_val;
g_variant_iter_init(&i, properties);
while(g_variant_iter_next(&i, "{&sv}", &property_name, &prop_val))
bluez_property_value(property_name, prop_val);
g_variant_unref(prop_val);
}
g_variant_unref(properties);
}
return;
}
#define BT_ADDRESS_STRING_SIZE 18
static void bluez_device_disappeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
char address[BT_ADDRESS_STRING_SIZE] = {'\0'};
g_variant_get(parameters, "(&oas)", &object, &interfaces);
while(g_variant_iter_next(interfaces, "s", &interface_name)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
int i;
char *tmp = g_strstr_len(object, -1, "dev_") + 4;
for(i = 0; *tmp != '\0'; i++, tmp++) {
if(*tmp == '_') {
address[i] = ':';
continue;
}
address[i] = *tmp;
}
g_print("\nDevice %s removed\n", address);
g_main_loop_quit((GMainLoop *)user_data);
}
}
return;
}
static void bluez_signal_adapter_changed(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *signal,
GVariant *params,
void *userdata)
{
(void)conn;
(void)sender;
(void)path;
(void)interface;
(void)userdata;
GVariantIter *properties = NULL;
GVariantIter *unknown = NULL;
const char *iface;
const char *key;
GVariant *value = NULL;
const gchar *signature = g_variant_get_type_string(params);
if(g_strcmp0(signature, "(sa{sv}as)") != 0) {
g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)");
goto done;
}
g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown);
while(g_variant_iter_next(properties, "{&sv}", &key, &value)) {
if(!g_strcmp0(key, "Powered")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter is Powered \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
if(!g_strcmp0(key, "Discovering")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter scan \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
}
done:
if(properties != NULL)
g_variant_iter_free(properties);
if(value != NULL)
g_variant_unref(value);
}
static int bluez_adapter_call_method(const char *method)
{
GVariant *result;
GError *error = NULL;
result = g_dbus_connection_call_sync(con,
"org.bluez",
/* TODO Find the adapter path runtime */
"/org/bluez/hci0",
"org.bluez.Adapter1",
method,
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_variant_unref(result);
return 0;
}
static int bluez_adapter_set_property(const char *prop, GVariant *value)
{
GVariant *result;
GError *error = NULL;
result = g_dbus_connection_call_sync(con,
"org.bluez",
"/org/bluez/hci0",
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new("(ssv)", "org.bluez.Adapter1", prop, value),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_variant_unref(result);
return 0;
}
int main(void)
{
GMainLoop *loop;
int rc;
guint prop_changed;
guint iface_added;
guint iface_removed;
con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
if(con == NULL) {
g_print("Not able to get connection to system bus\n");
return 1;
}
loop = g_main_loop_new(NULL, FALSE);
prop_changed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
NULL,
"org.bluez.Adapter1",
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_signal_adapter_changed,
NULL,
NULL);
iface_added = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_appeared,
loop,
NULL);
iface_removed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_disappeared,
loop,
NULL);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", TRUE));
if(rc) {
g_print("Not able to enable the adapter\n");
goto fail;
}
rc = bluez_adapter_call_method("StartDiscovery");
if(rc) {
g_print("Not able to scan for new devices\n");
goto fail;
}
g_main_loop_run(loop);
rc = bluez_adapter_call_method("StopDiscovery");
if(rc)
g_print("Not able to stop scanning\n");
g_usleep(100);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", FALSE));
if(rc)
g_print("Not able to disable the adapter\n");
fail:
g_dbus_connection_signal_unsubscribe(con, prop_changed);
g_dbus_connection_signal_unsubscribe(con, iface_added);
g_dbus_connection_signal_unsubscribe(con, iface_removed);
g_object_unref(con);
return 0;
}
Example to use SetDiscoveryFilter to scan only for BLE devices.
/*
* bluez_adapter_filter.c - Set discovery filter, Scan for bluetooth devices
* - Control three discovery filter parameter from command line,
* - auto/bredr/le
* - RSSI (0:very close range to -100:far away)
* - UUID (only one as of now)
* Example run: ./bin/bluez_adapter_filter bredr 100 00001105-0000-1000-8000-00805f9b34fb
* - This example scans for new devices after powering the adapter, if any devices
* appeared in /org/hciX/dev_XX_YY_ZZ_AA_BB_CC, it is monitered using "InterfaceAdded"
* signal and all the properties of the device is printed
* - Device will be removed immediately after it appears in InterfacesAdded signal, so
* InterfacesRemoved will be called which quits the main loop
* gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/bluez_adapter_filter ./bluez_adapter_filter.c `pkg-config --libs glib-2.0 gio-2.0`
*/
#include <glib.h>
#include <gio/gio.h>
GDBusConnection *con;
static void bluez_property_value(const gchar *key, GVariant *value)
{
const gchar *type = g_variant_get_type_string(value);
g_print("\t%s : ", key);
switch(*type) {
case 'o':
case 's':
g_print("%s\n", g_variant_get_string(value, NULL));
break;
case 'b':
g_print("%d\n", g_variant_get_boolean(value));
break;
case 'u':
g_print("%d\n", g_variant_get_uint32(value));
break;
case 'a':
/* TODO Handling only 'as', but not array of dicts */
if(g_strcmp0(type, "as"))
break;
g_print("\n");
const gchar *uuid;
GVariantIter i;
g_variant_iter_init(&i, value);
while(g_variant_iter_next(&i, "s", &uuid))
g_print("\t\t%s\n", uuid);
break;
default:
g_print("Other\n");
break;
}
}
typedef void (*method_cb_t)(GObject *, GAsyncResult *, gpointer);
static int bluez_adapter_call_method(const char *method, GVariant *param, method_cb_t method_cb)
{
GError *error = NULL;
g_dbus_connection_call(con,
"org.bluez",
/* TODO Find the adapter path runtime */
"/org/bluez/hci0",
"org.bluez.Adapter1",
method,
param,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
method_cb,
&error);
if(error != NULL)
return 1;
return 0;
}
static void bluez_get_discovery_filter_cb(GObject *con,
GAsyncResult *res,
gpointer data)
{
(void)data;
GVariant *result = NULL;
result = g_dbus_connection_call_finish((GDBusConnection *)con, res, NULL);
if(result == NULL)
g_print("Unable to get result for GetDiscoveryFilter\n");
if(result) {
result = g_variant_get_child_value(result, 0);
bluez_property_value("GetDiscoveryFilter", result);
}
g_variant_unref(result);
}
static void bluez_device_appeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
(void)user_data;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
GVariant *properties;
//int rc;
g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces);
while(g_variant_iter_next(interfaces, "{&s#a{sv}}", &interface_name, &properties)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
g_print("[ %s ]\n", object);
const gchar *property_name;
GVariantIter i;
GVariant *prop_val;
g_variant_iter_init(&i, properties);
while(g_variant_iter_next(&i, "{&sv}", &property_name, &prop_val))
bluez_property_value(property_name, prop_val);
g_variant_unref(prop_val);
}
g_variant_unref(properties);
}
/*
rc = bluez_adapter_call_method("RemoveDevice", g_variant_new("(o)", object));
if(rc)
g_print("Not able to remove %s\n", object);
*/
return;
}
#define BT_ADDRESS_STRING_SIZE 18
static void bluez_device_disappeared(GDBusConnection *sig,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
(void)sig;
(void)sender_name;
(void)object_path;
(void)interface;
(void)signal_name;
GVariantIter *interfaces;
const char *object;
const gchar *interface_name;
char address[BT_ADDRESS_STRING_SIZE] = {'\0'};
g_variant_get(parameters, "(&oas)", &object, &interfaces);
while(g_variant_iter_next(interfaces, "s", &interface_name)) {
if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) {
int i;
char *tmp = g_strstr_len(object, -1, "dev_") + 4;
for(i = 0; *tmp != '\0'; i++, tmp++) {
if(*tmp == '_') {
address[i] = ':';
continue;
}
address[i] = *tmp;
}
g_print("\nDevice %s removed\n", address);
g_main_loop_quit((GMainLoop *)user_data);
}
}
return;
}
static void bluez_signal_adapter_changed(GDBusConnection *conn,
const gchar *sender,
const gchar *path,
const gchar *interface,
const gchar *signal,
GVariant *params,
void *userdata)
{
(void)conn;
(void)sender;
(void)path;
(void)interface;
(void)userdata;
GVariantIter *properties = NULL;
GVariantIter *unknown = NULL;
const char *iface;
const char *key;
GVariant *value = NULL;
const gchar *signature = g_variant_get_type_string(params);
if(strcmp(signature, "(sa{sv}as)") != 0) {
g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)");
goto done;
}
g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown);
while(g_variant_iter_next(properties, "{&sv}", &key, &value)) {
if(!g_strcmp0(key, "Powered")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter is Powered \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
if(!g_strcmp0(key, "Discovering")) {
if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
g_print("Invalid argument type for %s: %s != %s", key,
g_variant_get_type_string(value), "b");
goto done;
}
g_print("Adapter scan \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off");
}
}
done:
if(properties != NULL)
g_variant_iter_free(properties);
if(value != NULL)
g_variant_unref(value);
}
static int bluez_adapter_set_property(const char *prop, GVariant *value)
{
GVariant *result;
GError *error = NULL;
result = g_dbus_connection_call_sync(con,
"org.bluez",
"/org/bluez/hci0",
"org.freedesktop.DBus.Properties",
"Set",
g_variant_new("(ssv)", "org.bluez.Adapter1", prop, value),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_variant_unref(result);
return 0;
}
static int bluez_set_discovery_filter(char **argv)
{
int rc;
GVariantBuilder *b = g_variant_builder_new(G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(b, "{sv}", "Transport", g_variant_new_string(argv[1]));
g_variant_builder_add(b, "{sv}", "RSSI", g_variant_new_int16(-g_ascii_strtod(argv[2], NULL)));
g_variant_builder_add(b, "{sv}", "DuplicateData", g_variant_new_boolean(FALSE));
GVariantBuilder *u = g_variant_builder_new(G_VARIANT_TYPE_STRING_ARRAY);
g_variant_builder_add(u, "s", argv[3]);
g_variant_builder_add(b, "{sv}", "UUIDs", g_variant_builder_end(u));
GVariant *device_dict = g_variant_builder_end(b);
g_variant_builder_unref(u);
g_variant_builder_unref(b);
rc = bluez_adapter_call_method("SetDiscoveryFilter", g_variant_new_tuple(&device_dict, 1), NULL);
if(rc) {
g_print("Not able to set discovery filter\n");
return 1;
}
rc = bluez_adapter_call_method("GetDiscoveryFilters",
NULL,
bluez_get_discovery_filter_cb);
if(rc) {
g_print("Not able to get discovery filter\n");
return 1;
}
return 0;
}
int main(int argc, char **argv)
{
GMainLoop *loop;
int rc;
guint prop_changed;
guint iface_added;
guint iface_removed;
con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
if(con == NULL) {
g_print("Not able to get connection to system bus\n");
return 1;
}
loop = g_main_loop_new(NULL, FALSE);
prop_changed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
NULL,
"org.bluez.Adapter1",
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_signal_adapter_changed,
NULL,
NULL);
iface_added = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_appeared,
loop,
NULL);
iface_removed = g_dbus_connection_signal_subscribe(con,
"org.bluez",
"org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
bluez_device_disappeared,
loop,
NULL);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", TRUE));
if(rc) {
g_print("Not able to enable the adapter\n");
goto fail;
}
if(argc > 3) {
rc = bluez_set_discovery_filter(argv);
if(rc)
goto fail;
}
rc = bluez_adapter_call_method("StartDiscovery", NULL, NULL);
if(rc) {
g_print("Not able to scan for new devices\n");
goto fail;
}
g_main_loop_run(loop);
if(argc > 3) {
rc = bluez_adapter_call_method("SetDiscoveryFilter", NULL, NULL);
if(rc)
g_print("Not able to remove discovery filter\n");
}
rc = bluez_adapter_call_method("StopDiscovery", NULL, NULL);
if(rc)
g_print("Not able to stop scanning\n");
g_usleep(100);
rc = bluez_adapter_set_property("Powered", g_variant_new("b", FALSE));
if(rc)
g_print("Not able to disable the adapter\n");
fail:
g_dbus_connection_signal_unsubscribe(con, prop_changed);
g_dbus_connection_signal_unsubscribe(con, iface_added);
g_dbus_connection_signal_unsubscribe(con, iface_removed);
g_object_unref(con);
return 0;
}
You can also find some explanation about StartDiscovery example here and SetDiscoveryFilter here.
I wrote some basic code to access the "start inquiry" bluez functionality by using dbus api's. Start Inquiry is happening that I could saw on hcidump. But I am not receiving any signal from the dbus which is "DeviceFound". I have tried lot. I tried to use different dbus tools like d-feet, dbus-monitor, bustle but I couln't got any clue.
Below is my written code. Anyone please tell me why this code is not working.
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus.h>
#include <glib.h>
struct generic_data {
unsigned int refcount;
GSList *interfaces;
char *introspect;
};
static void unregister(DBusConnection *connection, void *user_data)
{
}
static DBusHandlerResult filter_func(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
printf("Signal is called \n");
if (dbus_message_is_signal(message, "org.bluez.Adapter",
"DeviceFound"))
{
const char *adapter, *bdaddr;
char *name;
DBusMessageIter iter;
dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &bdaddr);
printf("Finally found device address is %s\n", bdaddr);
}
}
static DBusObjectPathVTable generic_table = {
.unregister_function = unregister,
.message_function = filter_func,
};
int main(int argc, char **argv) {
DBusConnection *conn;
DBusError error;
DBusMessage *msg, *reply,*signal;
dbus_bool_t hcid_exists,start;
DBusMessageIter reply_iter;
const char *name,*address;
char *adapter, *match;
DBusMessageIter iter;
struct generic_data *data;
va_list var_args;
static GMainLoop *loop = NULL;
conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
dbus_error_init(&error);
hcid_exists = dbus_bus_name_has_owner(conn, "org.bluez", &error);
if(hcid_exists)
printf("good news hurrey\n");
/* Get the default adapter */
msg = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "DefaultAdapter");
if (msg == NULL) {
dbus_connection_unref(conn);
return FALSE;
}
reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &error);
dbus_message_unref(msg);
if (dbus_error_is_set(&error))
{
dbus_connection_unref(conn);
}
dbus_message_iter_init(reply, &reply_iter);
if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_OBJECT_PATH)
{
dbus_message_unref(reply);
dbus_connection_unref(conn);
return FALSE;
}
dbus_message_iter_get_basic(&reply_iter, &adapter);
adapter = g_strdup(adapter);
//printf("Ohhhh gooood finally got adapter name %s\n",adapter);
dbus_message_unref(reply);
data = g_new0(struct generic_data, 1);
if (!dbus_connection_register_object_path(conn,adapter,&generic_table, data)) {
g_free(data->introspect);
g_free(data);
return FALSE;
}
if (!dbus_connection_add_filter(conn, filter_func, data, g_free))
{
g_free(adapter);
dbus_connection_unref(conn);
return FALSE;
}
if(conn!=NULL)
msg = dbus_message_new_method_call("org.bluez",adapter,"org.bluez.Adapter", "StartDiscovery");
else
printf("conn is failed\n");
if(msg!=NULL)
start = dbus_connection_send_with_reply(conn, msg,NULL,-1);
else
printf("msg is failed\n");
if(start)
{
//printf("Main llop hasd tp start\n");
loop = g_main_loop_new(NULL, TRUE);
g_main_loop_run(loop);
}
dbus_message_unref(msg);
dbus_message_unref(reply);
dbus_connection_close(conn);
return 0;
}
I tried to achieve at first a swf reader with gtk2 on my unix machine. Worked, I could render simple swf files. Now, I'm trying to add a configuration to my flash file with xml configuration, add image, etc. Failed, won't get pass geturlnotify().
Here's my code:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include "npupp.h"
#define FLASH_PLUGIN_SO "./libflashplayer.so"
void *flash_plugin_handle;
NPNetscapeFuncs browserFuncs;
NPPluginFuncs pluginFuncs;
GtkWidget *main_window;
char* fileName = NULL;
NPStream * stream;
const char * uagent = "Axt/1.0";
//Default window size
int WINDOW_XSIZE = 800;
int WINDOW_YSIZE = 600;
//Default child window position (flash player)
int xPosition = 0;
int yPosition = 0;
NPError (*iNP_Initialize)(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs);
NPError (*iNP_Shutdown)();
char* (*iNP_GetMIMEDescription)();
void* loadFlashPluginSo() {
void *handle;
handle = dlopen(FLASH_PLUGIN_SO, RTLD_LAZY | RTLD_LOCAL);
if(!handle) {
fprintf(stderr, "[-] error loading libflashplayer.so: %s\n", dlerror());
exit(1);
}
fprintf(stderr,"[+] loaded libflashplayer.so\n");
return handle;
}
void* loadSymbol(void *handle, const char *name) {
char *error;
void *ret;
ret = dlsym(handle, name);
if((error = dlerror()) != NULL) {
fprintf(stderr, "[-] error loading symbol %s: %s\n", name, error);
exit(1);
} else {
fprintf(stderr,"[+] loaded symbol %s, address: %p\n", name, ret);
}
return ret;
}
void loadNPEntryPoints(void *handle) {
iNP_Initialize=loadSymbol(handle, "NP_Initialize");
iNP_Shutdown=loadSymbol(handle, "NP_Shutdown");
iNP_GetMIMEDescription = loadSymbol(handle,"NP_GetMIMEDescription");
}
void printPluginEntrypoints(NPPluginFuncs* pFuncs) {
fprintf(stderr,"[*] NPP struct:\n");
fprintf(stderr,"\t- NPP_size: %8d\n",pFuncs->size);
fprintf(stderr,"\t- NPP_version: %8d\n",pFuncs->version);
fprintf(stderr,"\t- NPP_NewProcPtr: %p\n", pFuncs->newp);
fprintf(stderr,"\t- NPP_DestroyProcPtr: %p\n", pFuncs->destroy);
fprintf(stderr,"\t- NPP_SetWindowProcPtr: %p\n", pFuncs->setwindow);
fprintf(stderr,"\t- NPP_NewStreamProcPtr: %p\n", pFuncs->newstream);
fprintf(stderr,"\t- NPP_DestroyStreamProcPtr: %p\n", pFuncs->destroystream);
fprintf(stderr,"\t- NPP_StreamAsFileProcPtr: %p\n", pFuncs->asfile);
fprintf(stderr,"\t- NPP_WriteReadyProcPtr: %p\n", pFuncs->writeready);
fprintf(stderr,"\t- NPP_WriteProcPtr: %p\n", pFuncs->write);
fprintf(stderr,"\t- NPP_PrintProcPtr: %p\n", pFuncs->print);
fprintf(stderr,"\t- NPP_HandleEventProcPtr: %p\n", pFuncs->event);
fprintf(stderr,"\t- NPP_URLNotifyProcPtr: %p\n", pFuncs->urlnotify);
fprintf(stderr,"\t- javaClass: %p\n", pFuncs->javaClass);
fprintf(stderr,"\t- NPP_GetValueProcPtr: %p\n", pFuncs->getvalue);
fprintf(stderr,"\t- NPP_SetValueProcPtr: %p\n", pFuncs->setvalue);
}
NPError NPN_GetValueProc(NPP instance, NPNVariable variable, void *ret_value) {
fprintf(stderr,"[D] NPN_GetValueProc instance:%p, variable:%d, abi_mask:%d\n", instance, variable, 0);
switch (variable) {
case NPNVSupportsXEmbedBool:
*((int*)ret_value)= PR_TRUE;
break;
//Unix and solaris fix
case NPNVToolkit:
*((int*)ret_value)= NPNVGtk2;
break;
case NPNVnetscapeWindow:
*((int*)ret_value)= PR_TRUE;
break;
default:
*((int*)ret_value)=PR_FALSE;
break;
}
return NPERR_NO_ERROR;
}
const char* NPN_UserAgentProc(NPP instance) {
fprintf(stderr,"[D] NPN_UserAgentProc instance:%p\n", instance);
return uagent;
}
NPError NPN_GetURLProc(NPP instance, const char* url, const char* window) {
fprintf(stderr,"[D] NPN_GetURLProcPtr:%p, url: %s, window: %s\n", instance, url, window);
return NPERR_NO_ERROR;
}
NPIdentifier NPN_GetStringIdentifierProc(const NPUTF8* name) {
return (NPIdentifier)0x41424344; //Unique
}
static
gboolean plug_removed_cb (GtkWidget *widget, gpointer data) {
fprintf(stderr,"[!] plug_removed_cb\n");
return TRUE;
}
static void
socket_unrealize_cb(GtkWidget *widget, gpointer data) {
fprintf(stderr, "[!] socket_unrealize_cb\n");
}
static NPWindow *
npwindow_construct (GtkWidget *widget) {
NPWindow *npwindow;
NPSetWindowCallbackStruct *ws_info = NULL;
GdkWindow *parent_win = widget->window;
GtkWidget *socketWidget = gtk_socket_new();
gtk_widget_set_parent_window(socketWidget, parent_win);
gtk_widget_set_uposition(socketWidget, xPosition, yPosition);
g_signal_connect(socketWidget, "plug_removed", G_CALLBACK(plug_removed_cb), NULL);
g_signal_connect(socketWidget, "unrealize", G_CALLBACK(socket_unrealize_cb), NULL);
g_signal_connect(socketWidget, "destroy", G_CALLBACK(gtk_widget_destroyed), &socketWidget);
gpointer user_data = NULL;
gdk_window_get_user_data(parent_win, &user_data);
GtkContainer *container = GTK_CONTAINER(user_data);
gtk_container_add(container, socketWidget);
gtk_widget_realize(socketWidget);
GtkAllocation new_allocation;
new_allocation.x = 0;
new_allocation.y = 0;
new_allocation.width = WINDOW_XSIZE;
new_allocation.height = WINDOW_YSIZE;
gtk_widget_size_allocate(socketWidget, &new_allocation);
gtk_widget_show(socketWidget);
gdk_flush();
GdkNativeWindow ww = gtk_socket_get_id(GTK_SOCKET(socketWidget));
GdkWindow *w = gdk_window_lookup(ww);
npwindow = malloc (sizeof (NPWindow));
npwindow->window = (void*)(unsigned long)ww;
npwindow->x = 0;
npwindow->y = 0;
npwindow->width = WINDOW_XSIZE;
npwindow->height = WINDOW_YSIZE;
ws_info = malloc(sizeof (NPSetWindowCallbackStruct));
ws_info->type = NP_SETWINDOW;
ws_info->display = GDK_WINDOW_XDISPLAY(w);
ws_info->colormap = GDK_COLORMAP_XCOLORMAP(gdk_drawable_get_colormap(w));
GdkVisual* gdkVisual = gdk_drawable_get_visual(w);
ws_info->visual = GDK_VISUAL_XVISUAL(gdkVisual);
ws_info->depth = gdkVisual->depth;
npwindow->ws_info = ws_info;
npwindow->type = NPWindowTypeWindow;
return npwindow;
}
static NPStream *
npstream_construct() {
NPStream *stream = malloc(sizeof(NPStream));
stream->url=fileName;
stream->notifyData = 0x00000000;
fprintf(stderr,"[D] NPN_StreamConstructed: %p\n", stream);
return stream;
}
bool NPN_GetPropertyProc(NPP npp, NPObject *obj, NPIdentifier propertyName, NPVariant *result) {
fprintf(stderr,"[D] NPN_GetPropertyProc: %p\n", result);
result->type = NPVariantType_Object;
result->value.objectValue= (NPObject*)1;
return TRUE;
}
bool NPN_InvokeProc(NPP npp, NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t argCount, NPVariant *result) {
fprintf(stderr,"[D] NPN_InvokeProc: %p\n", result);
result->type= NPVariantType_String;
result->value.stringValue.utf8characters=fileName;
result->value.stringValue.utf8length=strlen(fileName);
return TRUE;
}
void NPN_ReleaseVariantValueProc(NPVariant *variant) {
}
void NPN_ReleaseObjectProc(NPObject *obj) {
}
NPObject* NPN_CreateObjectProc(NPP npp, NPClass *aClass) {
return (NPObject*)1;
}
NPObject* NPN_RetainObjectProc(NPObject *obj) {
return (NPObject*)1;
}
NPError NPN_GetURLNotifyProc(NPP instance, const char* url, const char* window, void* notifyData) {
fprintf(stderr,"[D] NPN_GetURLNotifyProc:%p, url: %s, window: %s\n", instance, url, window);
return 0;
}
NPN_GetURL, NPN_GetURLNotify, and NPP_URLNotify
void initNPNetscapeFuncs(NPNetscapeFuncs *bFuncs) {
int i=0;
for(i=1; i<sizeof(*bFuncs)/sizeof(ssize_t); i++)
*(((ssize_t*)bFuncs)+i)=i+1000;
bFuncs->geturl=NPN_GetURLProc;
bFuncs->getvalue=NPN_GetValueProc;
bFuncs->uagent=NPN_UserAgentProc;
bFuncs->getproperty=NPN_GetPropertyProc;
bFuncs->getstringidentifier=NPN_GetStringIdentifierProc;
bFuncs->invoke=NPN_InvokeProc;
bFuncs->releasevariantvalue=NPN_ReleaseVariantValueProc;
bFuncs->releaseobject=NPN_ReleaseObjectProc;
bFuncs->createobject=NPN_CreateObjectProc;
bFuncs->retainobject=NPN_RetainObjectProc;
bFuncs->geturlnotify=NPN_GetURLNotifyProc;
bFuncs->size= sizeof(bFuncs);
bFuncs->version= (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;;
}
static void destroy(GtkWidget *widget, gpointer data) {
gtk_main_quit ();
}
static void checkError(const char* str, NPError err) {
if(err == NPERR_NO_ERROR)
fprintf(stderr, "[+] %s: success\n", str);
else
fprintf(stderr, "[-] %s: failed (%d)\n", str, err);
fflush(NULL);
}
int main(int argc, char **argv)
{
int c;
extern char *optarg;
while ((c = getopt(argc, argv, "f:w:h:x:y:")) != EOF){
switch (c) {
case 'f':
fileName = optarg;
fprintf (stderr, "[+] Filename: %s\n", optarg);
break;
case 'w':
WINDOW_XSIZE = atoi(optarg);
fprintf (stderr, "[+] WINDOW_XSIZE: %s\n", optarg);
break;
case 'h':
WINDOW_YSIZE = atoi(optarg);
fprintf (stderr, "[+] WINDOW_YSIZE: %s\n", optarg);
break;
case 'x':
xPosition = atoi(optarg);
fprintf (stderr, "[+] Position in x: %s\n", optarg);
break;
case 'y':
yPosition = atoi(optarg);
fprintf (stderr, "[+] Position in y: %s\n", optarg);
break;
case '?':
if (optopt == 'f' | optopt == 'w' | optopt == 'h')
fprintf (stderr, "[-] Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf (stderr, "[-] Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,"[-] Unknown option character `\\x%x'.\n", optopt);
exit(-1);
default:
fprintf(stderr,"[-] Usage: %s -f <swffile> -x <xsize> -y <ysize>\n", argv[0]);
exit(-1);
}
}
gtk_init (&argc, &argv);
main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_usize (main_window, WINDOW_XSIZE, WINDOW_YSIZE);
gtk_widget_set_uposition (main_window, xPosition, yPosition);
g_signal_connect (G_OBJECT (main_window), "destroy", G_CALLBACK (destroy), NULL);
gtk_widget_realize(main_window);
gtk_widget_show_all(main_window);
fprintf(stderr,"[+] created GTK widget\n");
flash_plugin_handle = loadFlashPluginSo();
loadNPEntryPoints(flash_plugin_handle);
fprintf(stderr,"[+] initialized flash plugin entry points\n");
initNPNetscapeFuncs(&browserFuncs);
fprintf(stderr,"[+] initialized browser functions\n");
checkError("NP_Initialize", iNP_Initialize(&browserFuncs, &pluginFuncs));
printPluginEntrypoints(&pluginFuncs);
NPWindow *npwin = npwindow_construct(main_window);
fprintf(stderr,"[+] created NPWindow widget\n");
NPP_t *instancep = malloc(sizeof(NPP_t));
memset(instancep,0,sizeof(sizeof(NPP_t)));
NPP instance = instancep;
NPSavedData* saved = malloc(sizeof(NPSavedData));
memset(saved,0,sizeof(sizeof(NPSavedData)));
stream = npstream_construct();
uint16_t stype;
char *xargv[]= {"quality", "bgcolor", "width", "height", "allowScriptAccess", "loop" };
char *xargm[]= {"high", "#000000", "1360", "768", "always", "false" };
checkError("NPN_New", pluginFuncs.newp("application/x-shockwave-flash", instance, NP_EMBED, 0, xargv, xargm, saved));
checkError("NPN_SetWindow", pluginFuncs.setwindow(instance, npwin));
checkError("NPN_NewStream", pluginFuncs.newstream(instance, "application/x-shockwave-flash", stream, 0, &stype));
FILE *pp;
char buffer[8192];
pp = fopen(fileName,"rb");
int len;
while((len=fread(buffer, 1, sizeof(buffer), pp)) != 0) {
pluginFuncs.writeready(instance, stream);
pluginFuncs.write(instance, stream, 0, len, buffer);
}
fclose(pp);
checkError("NPN_DestroyStream",pluginFuncs.destroystream(instance, stream, NPRES_DONE));
free(stream);
gtk_main ();
checkError("NPN_Destroy",pluginFuncs.destroy(instance, &saved));
checkError("NP_Shutdown", iNP_Shutdown());
dlclose(flash_plugin_handle);
return 0;
}
If I load a swf with no xml. It works.
When I had a config to my xml. It stops.
Here's the ouput I get:
~/test-flash$ ./test-flash -f xml_sample.swf -w 400 -h 600 -x 15 -y 15
[+] Filename: xml_sample.swf
[+] WINDOW_XSIZE: 400
[+] WINDOW_YSIZE: 600
[+] Position in x: 15
[+] Position in y: 15
[+] created GTK widget
[+] loaded libflashplayer.so
[+] loaded symbol NP_Initialize, address: 0x7ff03fb634d0
[+] loaded symbol NP_Shutdown, address: 0x7ff03fb634c0
[+] loaded symbol NP_GetMIMEDescription, address: 0x7ff03fb63870
[+] initialized flash plugin entry points
[+] initialized browser functions
[+] NP_Initialize: success
[*] NPP struct:
- NPP_size: 0
- NPP_version: 0
- NPP_NewProcPtr: 0x7ff03fb63990
- NPP_DestroyProcPtr: 0x7ff03fb63980
- NPP_SetWindowProcPtr: 0x7ff03fb63970
- NPP_NewStreamProcPtr: 0x7ff03fb63960
- NPP_DestroyStreamProcPtr: 0x7ff03fb63910
- NPP_StreamAsFileProcPtr: 0x7ff03fb63920
- NPP_WriteReadyProcPtr: 0x7ff03fb63950
- NPP_WriteProcPtr: 0x7ff03fb63940
- NPP_PrintProcPtr: 0x7ff03fb63900
- NPP_HandleEventProcPtr: 0x7ff03fb638f0
- NPP_URLNotifyProcPtr: 0x7ff03fb63930
- javaClass: (nil)
- NPP_GetValueProcPtr: 0x7ff03fb63860
- NPP_SetValueProcPtr: (nil)
[+] created NPWindow widget
[D] NPN_StreamConstructed: 0x25ce5e0
[D] NPN_GetValueProc instance:0x25cc720, variable:14, abi_mask:0
[D] NPN_GetValueProc instance:0x25cc720, variable:268435469, abi_mask:0
[D] NPN_UserAgentProc instance:(nil)
[D] NPN_GetValueProc instance:0x25cc720, variable:15, abi_mask:0
[D] NPN_GetValueProc instance:0x25cc720, variable:15, abi_mask:0
[D] NPN_GetValueProc instance:0x25cc720, variable:18, abi_mask:0
[+] NPN_New: success
[D] NPN_GetValueProc instance:0x25cc720, variable:14, abi_mask:0
[+] NPN_SetWindow: success
[D] NPN_GetURLNotifyProc:0x25cc720, url: javascript:top.location+"__flashplugin_unique__", window: (null)
[D] NPN_GetValueProc instance:0x25cc720, variable:15, abi_mask:0
[+] NPN_NewStream: success
Lenght: 455
[D] NPN_UserAgentProc instance:0x25cc720
[+] NPN_DestroyStream: success
[D] NPN_GetURLNotifyProc:0x25cc720, url: sample.xml, window: (null)
Thank you!
EDIT:
I tried something like this (code is in NPN_GetURLNotifyProc function) . but then, my app freeze inside my geturlnotifyproc...
NPStream s;
uint16 stype;
memset(&s,0,sizeof(NPStream));
s.url = strdup(url);
fprintf(stderr, "URL: %s\n", s.url);
checkError("NPN_NewStream", pluginFuncs.newstream(instance,"text/html",&s,0,&stype));
writeStream(instance, &pluginFuncs, &s);
pluginFuncs.urlnotify(instance,url,NPRES_DONE,notifyData);
checkError("NPN_DestroyStream", pluginFuncs.destroystream(instance,&s,NPRES_DONE));
free((void*)s.url);
I forgot to add the notify to my stream...
NPError NPN_GetURLNotifyProc(NPP instance, const char* url, const char* target, void* notifyData) {
fprintf(stderr,"[D] NPN_GetURLNotifyProc:%p, url: %s, window: %s, data: %p\n", instance, url, target, notifyData);
NPStream s;
uint16 stype;
memset(&s,0,sizeof(NPStream));
s.url = strdup(url);
s.notifyData = notifyData;
fprintf(stderr, "NPP: %p URL: %s\n", instance, url);
checkError("NPN_NewStream", pluginFuncs.newstream(instance,"text/html",&s,0,&stype));
writeStream(instance, &pluginFuncs, &s);
checkError("NPN_DestroyStream", pluginFuncs.destroystream(instance,&s,NPRES_DONE));
free((void*)s.url);
pluginFuncs.urlnotify(instance, url, NPRES_DONE, notifyData);
return 0;
}
I have now a standalone gtk application who can run compiled flash (swf).
Hope it helps someone in the future. I recommend doing a plugin to read another plugin (aka Sniffer) to actually know which function is called ("and how things works") to have a decent log file to work with.