bluez-profile 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENCE +22 -0
- data/README.md +189 -0
- data/ext/bluez/extconf.rb +13 -0
- data/ext/bluez/profile.c +448 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 768299afaa1258a583149f1ff896b6522159c49c2d0090d57491f144a9cfa94b
|
4
|
+
data.tar.gz: d69e4bb1204ceb41f3ab4710a5bfb7948407533455f54c8d7b45c375f62f67b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e48632a5208e5f08973bbad6fde0ee347edf685ddff1dd2ab69db1e1edfac3f909390fe8809158bd2911945d23fcb0ebb75818676f87369fd012e130e85a4d4
|
7
|
+
data.tar.gz: fbd115fee66256f7115f63f11a82830e96c72387eb1528101a2562b73aad34535ead60b722afd65d906a2ce117deb6308364366e05f73e927fcaae8dd6de50cc
|
data/LICENCE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2021 Clive Andrews
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# bluez Profile API bindings
|
2
|
+
|
3
|
+
this gem allows easy creation of bluetooth services.
|
4
|
+
For example create a serial link over to another device using bluetooth.
|
5
|
+
|
6
|
+
https://github.com/pauloborges/bluez/blob/master/doc/profile-api.txt
|
7
|
+
|
8
|
+
|
9
|
+
Clive Andrews 2021
|
10
|
+
|
11
|
+
## dependencies
|
12
|
+
|
13
|
+
* Linux/Unix type system
|
14
|
+
* bluez 5
|
15
|
+
* glib-2 (dev)
|
16
|
+
|
17
|
+
## prerequisites
|
18
|
+
|
19
|
+
your bluetooth devices must be paired and trusted outside of this gem
|
20
|
+
in order for them to communicate.
|
21
|
+
|
22
|
+
use eg `bluetoothctl`
|
23
|
+
|
24
|
+
## API
|
25
|
+
|
26
|
+
example code:
|
27
|
+
|
28
|
+
class MyProfile < Bluez::Profile
|
29
|
+
|
30
|
+
# This method gets called when the service daemon
|
31
|
+
# unregisters the profile. A profile can use it to do
|
32
|
+
# cleanup tasks.
|
33
|
+
|
34
|
+
def release
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# This method gets called when a new service level
|
39
|
+
# connection has been made and authorized.
|
40
|
+
|
41
|
+
def connection(device, fd, fd_properties)
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method gets called when a profile gets
|
46
|
+
# disconnected.
|
47
|
+
#
|
48
|
+
# The file descriptor is no longer owned by the service
|
49
|
+
# daemon and the profile implementation needs to take
|
50
|
+
# care of cleaning up all connections.
|
51
|
+
#
|
52
|
+
# If multiple file descriptors are indicated via
|
53
|
+
# NewConnection, it is expected that all of them
|
54
|
+
# are disconnected before returning from this
|
55
|
+
# method call.
|
56
|
+
|
57
|
+
def disconnection(device)
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
path = '/my/dbus/path'
|
64
|
+
uuid = '1101'
|
65
|
+
|
66
|
+
options = {
|
67
|
+
name: 'my profile',
|
68
|
+
channel: 3
|
69
|
+
connect: false
|
70
|
+
}
|
71
|
+
|
72
|
+
profile = MyProfile.new(path, uuid, options)
|
73
|
+
profile.run # enters loop and blocks here.
|
74
|
+
|
75
|
+
profile.stop # stops the loop
|
76
|
+
|
77
|
+
|
78
|
+
path: string
|
79
|
+
|
80
|
+
the dbus object path of the profile.
|
81
|
+
eg: "/serial/special/profile"
|
82
|
+
|
83
|
+
uuid: string
|
84
|
+
|
85
|
+
the dbus profile uuid
|
86
|
+
|
87
|
+
|
88
|
+
Available options:
|
89
|
+
|
90
|
+
name: string
|
91
|
+
|
92
|
+
Human readable name for the profile
|
93
|
+
|
94
|
+
service: string
|
95
|
+
|
96
|
+
The primary service class UUID
|
97
|
+
(if different from the actual
|
98
|
+
profile UUID)
|
99
|
+
|
100
|
+
role: string
|
101
|
+
|
102
|
+
For asymmetric profiles that do not
|
103
|
+
have UUIDs available to uniquely
|
104
|
+
identify each side this
|
105
|
+
parameter allows specifying the
|
106
|
+
precise local role.
|
107
|
+
|
108
|
+
Possible values:
|
109
|
+
|
110
|
+
Bluez::Profile::Client
|
111
|
+
Bluez::Profile::Server
|
112
|
+
|
113
|
+
channel: int
|
114
|
+
|
115
|
+
RFCOMM channel number that is used
|
116
|
+
for client and server UUIDs.
|
117
|
+
|
118
|
+
If applicable it will be used in the
|
119
|
+
SDP record as well.
|
120
|
+
|
121
|
+
psm: int
|
122
|
+
|
123
|
+
PSM number that is used for client
|
124
|
+
and server UUIDs.
|
125
|
+
|
126
|
+
If applicable it will be used in the
|
127
|
+
SDP record as well.
|
128
|
+
|
129
|
+
authentication: boolean
|
130
|
+
|
131
|
+
Pairing is required before connections
|
132
|
+
will be established. No devices will
|
133
|
+
be connected if not paired.
|
134
|
+
|
135
|
+
authorization: boolean
|
136
|
+
|
137
|
+
Request authorization before any
|
138
|
+
connection will be established.
|
139
|
+
|
140
|
+
connect: boolean
|
141
|
+
|
142
|
+
In case of a client UUID this will
|
143
|
+
force connection of the RFCOMM or
|
144
|
+
L2CAP channels when a remote device
|
145
|
+
is connected.
|
146
|
+
|
147
|
+
record: string
|
148
|
+
|
149
|
+
Provide a manual SDP record.
|
150
|
+
|
151
|
+
version: int
|
152
|
+
|
153
|
+
Profile version (for SDP record)
|
154
|
+
|
155
|
+
features: int
|
156
|
+
|
157
|
+
Profile features (for SDP record)
|
158
|
+
|
159
|
+
## server
|
160
|
+
|
161
|
+
If a channel number is already in use your service may not be registered.
|
162
|
+
Check that your services have been registered correctly with eg:
|
163
|
+
|
164
|
+
sudo sdptool browse local
|
165
|
+
|
166
|
+
## examples
|
167
|
+
|
168
|
+
See the `/examples` folder.
|
169
|
+
|
170
|
+
## issues
|
171
|
+
|
172
|
+
The server side of things seems to work fine.
|
173
|
+
|
174
|
+
run several servers over the same serial service using different
|
175
|
+
channel numbers
|
176
|
+
|
177
|
+
on the client ( paired and trusted) connect each channel eg :
|
178
|
+
|
179
|
+
sudo rfcomm connect /dev/rfcomm0 remoteaddr channel1
|
180
|
+
sudo rfcomm connect /dev/rfcomm1 remoteaddr channel2
|
181
|
+
sudo rfcomm connect /dev/rfcomm2 remoteaddr channel3
|
182
|
+
|
183
|
+
this works perfectly. each device file allows interaction with the correct server.
|
184
|
+
|
185
|
+
*BUT* if on the client a profile is registered with bluez using this gem
|
186
|
+
and a connection made to the server then things get in a muddle and output
|
187
|
+
for the one channel gets sent to the wrong profile !!
|
188
|
+
|
189
|
+
any ideas ??
|
data/ext/bluez/profile.c
ADDED
@@ -0,0 +1,448 @@
|
|
1
|
+
// this ruby binding allows a profile to be registered
|
2
|
+
// with the bluez stack.
|
3
|
+
//
|
4
|
+
// (c) C. Andrews 2021
|
5
|
+
//
|
6
|
+
// References:
|
7
|
+
//
|
8
|
+
// https://github.com/pauloborges/bluez/blob/master/doc/profile-api.txt
|
9
|
+
// https://github.com/tonyespy/bluez5-spp-example
|
10
|
+
// https://github.com/bratsche/glib/blob/master/gio/tests/gdbus-example-server.c
|
11
|
+
|
12
|
+
|
13
|
+
#include <gio/gio.h>
|
14
|
+
#include <gio/gunixfdlist.h>
|
15
|
+
#include <stdlib.h>
|
16
|
+
#include <ruby.h>
|
17
|
+
|
18
|
+
#define BLUEZ_BUS_NAME "org.bluez"
|
19
|
+
#define BLUEZ_BUS_PATH "/org/bluez"
|
20
|
+
#define BLUEZ_INTERFACE_DEVICE1 "org.bluez.Device1"
|
21
|
+
#define BLUEZ_INTERFACE_PROFILE1 "org.bluez.Profile1"
|
22
|
+
#define BLUEZ_INTERFACE_PROFILEMANAGER1 "org.bluez.ProfileManager1"
|
23
|
+
|
24
|
+
static VALUE mBluez;
|
25
|
+
static VALUE cProfile;
|
26
|
+
static VALUE eProfileError;
|
27
|
+
|
28
|
+
typedef struct t_profile_data{
|
29
|
+
GDBusConnection* conn;
|
30
|
+
GDBusProxy* proxy;
|
31
|
+
GDBusNodeInfo* introspection_data;
|
32
|
+
guint id;
|
33
|
+
GMainLoop* loop;
|
34
|
+
int running;
|
35
|
+
int fd;
|
36
|
+
} t_profile_data;
|
37
|
+
|
38
|
+
|
39
|
+
/* Introspection data for the service we are exporting */
|
40
|
+
static const gchar introspection_xml[] =
|
41
|
+
"<node>"
|
42
|
+
" <interface name='org.bluez.Profile1'>"
|
43
|
+
" <method name='Release'>"
|
44
|
+
" </method>"
|
45
|
+
" <method name='NewConnection'>"
|
46
|
+
" <arg type='o' name='device' direction='in'/>"
|
47
|
+
" <arg type='h' name='fd' direction='in'/>"
|
48
|
+
" <arg type='a{sv}' name='fd_properties' direction='in'/>"
|
49
|
+
" </method>"
|
50
|
+
" <method name='RequestDisconnection'>"
|
51
|
+
" <arg type='o' name='device' direction='in'/>"
|
52
|
+
" </method>"
|
53
|
+
" </interface>"
|
54
|
+
"</node>";
|
55
|
+
|
56
|
+
// free the c data structure
|
57
|
+
|
58
|
+
static void profile_free_data(t_profile_data* p){
|
59
|
+
if (p->id !=0 ) g_dbus_connection_unregister_object(p->conn, p->id);
|
60
|
+
if (p->introspection_data !=NULL ) g_dbus_node_info_unref (p->introspection_data);
|
61
|
+
if (p->proxy !=NULL ) g_object_unref (p->proxy);
|
62
|
+
if (p->conn !=NULL ) g_object_unref (p->conn);
|
63
|
+
if (p->loop !=NULL ) g_main_loop_unref(p->loop);
|
64
|
+
p->introspection_data = NULL;
|
65
|
+
p->proxy = NULL;
|
66
|
+
p->conn = NULL;
|
67
|
+
p->loop = NULL;
|
68
|
+
free(p);
|
69
|
+
}
|
70
|
+
|
71
|
+
// allocate the c data structure
|
72
|
+
|
73
|
+
static VALUE profile_alloc_data(VALUE self){
|
74
|
+
VALUE obj;
|
75
|
+
t_profile_data* data = malloc(sizeof(t_profile_data));
|
76
|
+
data->conn = NULL;
|
77
|
+
data->proxy = NULL;
|
78
|
+
data->introspection_data = NULL;
|
79
|
+
data->loop = NULL;
|
80
|
+
obj = Data_Wrap_Struct(self, 0, profile_free_data, data);
|
81
|
+
return obj;
|
82
|
+
}
|
83
|
+
|
84
|
+
// handle object method calls ----------------------------------------------
|
85
|
+
static void
|
86
|
+
handle_method_call (GDBusConnection *connection,
|
87
|
+
const gchar *sender,
|
88
|
+
const gchar *object_path,
|
89
|
+
const gchar *interface_name,
|
90
|
+
const gchar *method_name,
|
91
|
+
GVariant *parameters,
|
92
|
+
GDBusMethodInvocation *invocation,
|
93
|
+
gpointer user_data)
|
94
|
+
{
|
95
|
+
|
96
|
+
VALUE self;
|
97
|
+
GDBusMessage *message;
|
98
|
+
GUnixFDList *fd_list;
|
99
|
+
GError *error = NULL;
|
100
|
+
gchar* path;
|
101
|
+
int fd_idx;
|
102
|
+
int fd;
|
103
|
+
GVariantIter *list;
|
104
|
+
|
105
|
+
self = (VALUE) user_data;
|
106
|
+
|
107
|
+
if (g_strcmp0 (method_name, "Release") == 0)
|
108
|
+
{
|
109
|
+
//printf("Release Called\n");
|
110
|
+
rb_funcall(self, rb_intern("release"), 0 );
|
111
|
+
}
|
112
|
+
else if (g_strcmp0 (method_name, "NewConnection") == 0)
|
113
|
+
// in_signature="oha{sv}"
|
114
|
+
{
|
115
|
+
g_variant_get (parameters, "(oha{sv})", &path, &fd_idx, &list);
|
116
|
+
message = g_dbus_method_invocation_get_message (invocation);
|
117
|
+
fd_list = g_dbus_message_get_unix_fd_list (message);
|
118
|
+
fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
|
119
|
+
if (error!=NULL){
|
120
|
+
rb_raise(eProfileError, "invalid file descriptor");
|
121
|
+
return;
|
122
|
+
}
|
123
|
+
rb_funcall(self, rb_intern("connection"), 2,rb_str_new2(path), INT2NUM(fd) );
|
124
|
+
}
|
125
|
+
else if (g_strcmp0 (method_name, "RequestDisconnection") == 0)
|
126
|
+
// in_signature="o"
|
127
|
+
{
|
128
|
+
//printf("RequestDisconnection Called\n");
|
129
|
+
g_variant_get (parameters, "o", &path);
|
130
|
+
rb_funcall(self, rb_intern("disconnection"), 1,rb_str_new2(path) );
|
131
|
+
}
|
132
|
+
|
133
|
+
}
|
134
|
+
|
135
|
+
// static GVariant *
|
136
|
+
// handle_get_property (GDBusConnection *connection,
|
137
|
+
// const gchar *sender,
|
138
|
+
// const gchar *object_path,
|
139
|
+
// const gchar *interface_name,
|
140
|
+
// const gchar *property_name,
|
141
|
+
// GError **error,
|
142
|
+
// gpointer user_data)
|
143
|
+
// {
|
144
|
+
// return NULL;
|
145
|
+
// }
|
146
|
+
//
|
147
|
+
// static gboolean
|
148
|
+
// handle_set_property (GDBusConnection *connection,
|
149
|
+
// const gchar *sender,
|
150
|
+
// const gchar *object_path,
|
151
|
+
// const gchar *interface_name,
|
152
|
+
// const gchar *property_name,
|
153
|
+
// GVariant *value,
|
154
|
+
// GError **error,
|
155
|
+
// gpointer user_data)
|
156
|
+
// {
|
157
|
+
// return TRUE;
|
158
|
+
// }
|
159
|
+
//
|
160
|
+
|
161
|
+
static const GDBusInterfaceVTable vtable =
|
162
|
+
{
|
163
|
+
handle_method_call,
|
164
|
+
NULL,
|
165
|
+
NULL
|
166
|
+
};
|
167
|
+
|
168
|
+
// register our profile object with dbus --------------------------------------
|
169
|
+
|
170
|
+
static guint register_object(VALUE self, t_profile_data* data, const gchar* path){
|
171
|
+
guint id;
|
172
|
+
GError *error = NULL;
|
173
|
+
|
174
|
+
id = g_dbus_connection_register_object (
|
175
|
+
data->conn,
|
176
|
+
path,
|
177
|
+
data->introspection_data->interfaces[0],
|
178
|
+
&vtable,
|
179
|
+
(gpointer) self, //user_data,
|
180
|
+
NULL, //user_data_free_func,
|
181
|
+
&error
|
182
|
+
);
|
183
|
+
|
184
|
+
return id;
|
185
|
+
}
|
186
|
+
|
187
|
+
// register our profile with bluez --------------------------------------------
|
188
|
+
|
189
|
+
int register_profile(VALUE path, VALUE uuid, VALUE options, GDBusProxy *proxy){
|
190
|
+
|
191
|
+
GVariant *profile;
|
192
|
+
GVariantBuilder profile_builder;
|
193
|
+
GError *error = NULL;
|
194
|
+
VALUE val;
|
195
|
+
|
196
|
+
//printf("register_profile called!\n");
|
197
|
+
|
198
|
+
g_variant_builder_init(&profile_builder, G_VARIANT_TYPE("(osa{sv})"));
|
199
|
+
|
200
|
+
g_variant_builder_add (&profile_builder, "o",StringValueCStr(path));
|
201
|
+
|
202
|
+
g_variant_builder_add (&profile_builder, "s", StringValueCStr(uuid));
|
203
|
+
|
204
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("a{sv}"));
|
205
|
+
|
206
|
+
// Name
|
207
|
+
|
208
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("name")));
|
209
|
+
if (val != Qnil){
|
210
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
211
|
+
g_variant_builder_add (&profile_builder, "s", "Name");
|
212
|
+
g_variant_builder_add (&profile_builder, "v",
|
213
|
+
g_variant_new_string(StringValueCStr(val) ));
|
214
|
+
g_variant_builder_close(&profile_builder);
|
215
|
+
}
|
216
|
+
|
217
|
+
// Service
|
218
|
+
|
219
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("service")));
|
220
|
+
if (val != Qnil){
|
221
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
222
|
+
g_variant_builder_add (&profile_builder, "s", "Service");
|
223
|
+
g_variant_builder_add (&profile_builder, "v",
|
224
|
+
g_variant_new_string(StringValueCStr(val) ));
|
225
|
+
g_variant_builder_close(&profile_builder);
|
226
|
+
}
|
227
|
+
|
228
|
+
// Role
|
229
|
+
|
230
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("role")));
|
231
|
+
if (val != Qnil){
|
232
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
233
|
+
g_variant_builder_add (&profile_builder, "s", "Role");
|
234
|
+
g_variant_builder_add (&profile_builder, "v",
|
235
|
+
g_variant_new_string(StringValueCStr(val) ));
|
236
|
+
g_variant_builder_close(&profile_builder);
|
237
|
+
}
|
238
|
+
|
239
|
+
// Channel
|
240
|
+
|
241
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("channel")));
|
242
|
+
if (val != Qnil){
|
243
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
244
|
+
g_variant_builder_add (&profile_builder, "s", "Channel");
|
245
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
246
|
+
g_variant_builder_close(&profile_builder);
|
247
|
+
}
|
248
|
+
|
249
|
+
// PSM
|
250
|
+
|
251
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("psm")));
|
252
|
+
if (val != Qnil){
|
253
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
254
|
+
g_variant_builder_add (&profile_builder, "s", "PSM");
|
255
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
256
|
+
g_variant_builder_close(&profile_builder);
|
257
|
+
}
|
258
|
+
|
259
|
+
// RequireAuthentication
|
260
|
+
|
261
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("authentication")));
|
262
|
+
if (val != Qnil){
|
263
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
264
|
+
g_variant_builder_add (&profile_builder, "s", "RequireAuthentication");
|
265
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
266
|
+
g_variant_builder_close(&profile_builder);
|
267
|
+
}
|
268
|
+
|
269
|
+
// RequireAuthorization
|
270
|
+
|
271
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("authorization")));
|
272
|
+
if (val != Qnil){
|
273
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
274
|
+
g_variant_builder_add (&profile_builder, "s", "RequireAuthorization");
|
275
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
276
|
+
g_variant_builder_close(&profile_builder);
|
277
|
+
}
|
278
|
+
|
279
|
+
// AutoConnect
|
280
|
+
|
281
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("connect")));
|
282
|
+
if (val != Qnil){
|
283
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
284
|
+
g_variant_builder_add (&profile_builder, "s", "AutoConnect");
|
285
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_boolean(RTEST(val) ));
|
286
|
+
g_variant_builder_close(&profile_builder);
|
287
|
+
}
|
288
|
+
|
289
|
+
// ServiceRecord
|
290
|
+
|
291
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("record")));
|
292
|
+
if (val != Qnil){
|
293
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
294
|
+
g_variant_builder_add (&profile_builder, "s", "ServiceRecord");
|
295
|
+
g_variant_builder_add (&profile_builder, "v",
|
296
|
+
g_variant_new_string(StringValueCStr(val) ));
|
297
|
+
g_variant_builder_close(&profile_builder);
|
298
|
+
}
|
299
|
+
|
300
|
+
// Version
|
301
|
+
|
302
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("version")));
|
303
|
+
if (val != Qnil){
|
304
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
305
|
+
g_variant_builder_add (&profile_builder, "s", "Version");
|
306
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
307
|
+
g_variant_builder_close(&profile_builder);
|
308
|
+
}
|
309
|
+
|
310
|
+
// Features
|
311
|
+
|
312
|
+
val = rb_hash_aref(options, ID2SYM(rb_intern("features")));
|
313
|
+
if (val != Qnil){
|
314
|
+
g_variant_builder_open(&profile_builder, G_VARIANT_TYPE("{sv}"));
|
315
|
+
g_variant_builder_add (&profile_builder, "s", "Features");
|
316
|
+
g_variant_builder_add (&profile_builder, "v", g_variant_new_uint16(FIX2INT(val)));
|
317
|
+
g_variant_builder_close(&profile_builder);
|
318
|
+
}
|
319
|
+
|
320
|
+
g_variant_builder_close(&profile_builder);
|
321
|
+
profile = g_variant_builder_end(&profile_builder);
|
322
|
+
|
323
|
+
g_dbus_proxy_call_sync (proxy,
|
324
|
+
"RegisterProfile",
|
325
|
+
profile,
|
326
|
+
G_DBUS_CALL_FLAGS_NONE,
|
327
|
+
-1,
|
328
|
+
NULL,
|
329
|
+
&error);
|
330
|
+
|
331
|
+
if (error!=NULL){
|
332
|
+
return 0;
|
333
|
+
}
|
334
|
+
|
335
|
+
return 1;
|
336
|
+
}
|
337
|
+
|
338
|
+
|
339
|
+
// initialize our profile here. ------------------------------------------------
|
340
|
+
|
341
|
+
static VALUE profile_initialize(VALUE self, VALUE path, VALUE uuid, VALUE options){
|
342
|
+
t_profile_data* data = NULL;
|
343
|
+
GError *error = NULL;
|
344
|
+
int ok;
|
345
|
+
const gchar * cpath = StringValueCStr(path);
|
346
|
+
Data_Get_Struct(self, t_profile_data, data);
|
347
|
+
|
348
|
+
// validate the path
|
349
|
+
|
350
|
+
if (!g_variant_is_object_path(cpath)) {
|
351
|
+
rb_raise(eProfileError, "invalid object path");
|
352
|
+
return Qnil;
|
353
|
+
}
|
354
|
+
|
355
|
+
data->loop = g_main_loop_new (NULL, FALSE);
|
356
|
+
data->running = FALSE;
|
357
|
+
|
358
|
+
data->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
|
359
|
+
if (data->introspection_data==NULL){
|
360
|
+
rb_raise(eProfileError, "error generating introspection data");
|
361
|
+
return Qnil;
|
362
|
+
}
|
363
|
+
|
364
|
+
// connect to dbus system bus
|
365
|
+
data->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
|
366
|
+
if (error!=NULL){
|
367
|
+
rb_raise(eProfileError, "error connecting to dbus");
|
368
|
+
return Qnil;
|
369
|
+
}
|
370
|
+
|
371
|
+
// obtain a proxy to bluez profile manager.
|
372
|
+
|
373
|
+
data->proxy = g_dbus_proxy_new_sync (data->conn,
|
374
|
+
G_DBUS_PROXY_FLAGS_NONE,
|
375
|
+
NULL,/* GDBusInterfaceInfo */
|
376
|
+
BLUEZ_BUS_NAME,/* name */
|
377
|
+
BLUEZ_BUS_PATH,/* object path */
|
378
|
+
BLUEZ_INTERFACE_PROFILEMANAGER1,/* interface */
|
379
|
+
NULL,/* GCancellable */
|
380
|
+
&error);
|
381
|
+
|
382
|
+
if (error!=NULL){
|
383
|
+
rb_raise(eProfileError, "error connecting to bluez profile manager");
|
384
|
+
return Qnil;
|
385
|
+
}
|
386
|
+
|
387
|
+
// register our profile object with dbus
|
388
|
+
|
389
|
+
data->id = register_object(self, data, cpath);
|
390
|
+
|
391
|
+
if (data->id==0){
|
392
|
+
rb_raise(eProfileError, "error registering profile object");
|
393
|
+
return Qnil;
|
394
|
+
}
|
395
|
+
|
396
|
+
// and register the profile with the profile manager.
|
397
|
+
|
398
|
+
ok = register_profile(path, uuid, options, data->proxy);
|
399
|
+
|
400
|
+
if (ok==0){
|
401
|
+
rb_raise(eProfileError, "error register profile with bluez");
|
402
|
+
return Qnil;
|
403
|
+
}
|
404
|
+
|
405
|
+
return self;
|
406
|
+
}
|
407
|
+
|
408
|
+
// run the event loop ----------------------------------------------------------
|
409
|
+
|
410
|
+
static VALUE profile_run(VALUE self){
|
411
|
+
t_profile_data* data = NULL;
|
412
|
+
GMainContext * context = g_main_context_default();
|
413
|
+
Data_Get_Struct(self, t_profile_data, data);
|
414
|
+
if (!data->running){
|
415
|
+
data->running = TRUE;
|
416
|
+
while(data->running){
|
417
|
+
g_main_context_iteration (context, FALSE);
|
418
|
+
rb_thread_schedule();
|
419
|
+
}
|
420
|
+
}
|
421
|
+
return self;
|
422
|
+
}
|
423
|
+
|
424
|
+
// stop the event loop ---------------------------------------------------------
|
425
|
+
|
426
|
+
static VALUE profile_stop(VALUE self){
|
427
|
+
t_profile_data* data = NULL;
|
428
|
+
Data_Get_Struct(self, t_profile_data, data);
|
429
|
+
data->running = FALSE;
|
430
|
+
return self;
|
431
|
+
}
|
432
|
+
|
433
|
+
//=============================== ruby interface definition ====================
|
434
|
+
|
435
|
+
void Init_profile() {
|
436
|
+
|
437
|
+
mBluez = rb_define_module("Bluez");
|
438
|
+
cProfile = rb_define_class_under(mBluez, "Profile", rb_cObject);
|
439
|
+
eProfileError = rb_define_class_under(cProfile, "Error",rb_eStandardError);
|
440
|
+
|
441
|
+
rb_define_const(cProfile, "Client", rb_str_new_cstr("client"));
|
442
|
+
rb_define_const(cProfile, "Server", rb_str_new_cstr("server"));
|
443
|
+
|
444
|
+
rb_define_alloc_func(cProfile, profile_alloc_data);
|
445
|
+
rb_define_method(cProfile, "initialize", profile_initialize, 3);
|
446
|
+
rb_define_method(cProfile, "run", profile_run, 0);
|
447
|
+
rb_define_method(cProfile, "stop", profile_stop, 0);
|
448
|
+
}
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bluez-profile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Clive Andrews
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Create Custom Bluez Bluetooth Profiles
|
14
|
+
email:
|
15
|
+
- gems@realitybites.eu
|
16
|
+
executables: []
|
17
|
+
extensions:
|
18
|
+
- ext/bluez/extconf.rb
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
- LICENCE
|
22
|
+
files:
|
23
|
+
- LICENCE
|
24
|
+
- README.md
|
25
|
+
- ext/bluez/extconf.rb
|
26
|
+
- ext/bluez/profile.c
|
27
|
+
homepage: https://github.com/realbite/bluez-profile
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- ext
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.9.2
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubygems_version: 3.1.4
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: binding to Bluez Profile API
|
50
|
+
test_files: []
|