Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-bluez-device"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <valent.h>
9 : :
10 : : #include "valent-bluez-device.h"
11 : : #include "valent-bluez-profile.h"
12 : :
13 : : #define BLUEZ_NAME "org.bluez"
14 : : #define BLUEZ_DEVICE_NAME "org.bluez.Device1"
15 : :
16 : :
17 : : struct _ValentBluezDevice
18 : : {
19 : : GObject parent_instance;
20 : :
21 : : GCancellable *cancellable;
22 : : GDBusConnection *connection;
23 : : char *object_path;
24 : : unsigned int properties_changed_id;
25 : :
26 : : GStrv uuids;
27 : : gboolean connected;
28 : : gboolean paired;
29 : : };
30 : :
31 [ + + + - ]: 6 : G_DEFINE_FINAL_TYPE (ValentBluezDevice, valent_bluez_device, G_TYPE_OBJECT)
32 : :
33 : : typedef enum {
34 : : PROP_CONNECTION = 1,
35 : : PROP_OBJECT_PATH,
36 : : } ValentBluezDeviceProperty;
37 : :
38 : : static GParamSpec *properties[PROP_OBJECT_PATH + 1] = { NULL, };
39 : :
40 : :
41 : : static GWeakRef *
42 : 2 : weak_ref_new (GObject *object)
43 : : {
44 : 2 : GWeakRef *weak_ref;
45 : :
46 : 2 : weak_ref = g_new0 (GWeakRef, 1);
47 : 2 : g_weak_ref_init (weak_ref, object);
48 : :
49 : 2 : return g_steal_pointer (&weak_ref);
50 : : }
51 : :
52 : : static void
53 : 1 : weak_ref_free (gpointer data)
54 : : {
55 : 1 : GWeakRef *weak_ref = data;
56 : :
57 : 1 : g_weak_ref_clear (weak_ref);
58 : 1 : g_free (weak_ref);
59 : 1 : }
60 : :
61 : : static void
62 : 0 : on_properties_changed (GDBusConnection *connection,
63 : : const char *sender_name,
64 : : const char *object_path,
65 : : const char *interface_name,
66 : : const char *signal_name,
67 : : GVariant *parameters,
68 : : gpointer user_data)
69 : : {
70 : 0 : GWeakRef *proxy_weak = user_data;
71 : 0 : g_autoptr (ValentBluezDevice) self = NULL;
72 : 0 : const char *interface;
73 [ # # ]: 0 : g_autoptr (GVariant) changed = NULL;
74 [ # # # # ]: 0 : g_autofree char **invalidated = NULL;
75 : 0 : g_autoptr (GVariant) uuids = NULL;
76 : :
77 [ # # ]: 0 : if ((self = g_weak_ref_get (proxy_weak)) == NULL)
78 : : return;
79 : :
80 [ # # ]: 0 : if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
81 : : {
82 : 0 : g_warning ("Value for PropertiesChanged signal with type '%s' does not match '(sa{sv}as)'",
83 : : g_variant_get_type_string (parameters));
84 : 0 : return;
85 : : }
86 : :
87 : 0 : g_variant_get (parameters,
88 : : "(&s@a{sv}^a&s)",
89 : : &interface,
90 : : &changed,
91 : : &invalidated);
92 : :
93 [ # # ]: 0 : if (g_strcmp0 (interface, BLUEZ_DEVICE_NAME) != 0)
94 : : return;
95 : :
96 : 0 : g_variant_lookup (changed, "Connected", "b", &self->connected);
97 : 0 : g_variant_lookup (changed, "Paired", "b", &self->paired);
98 : :
99 : 0 : uuids = g_variant_lookup_value (changed, "UUIDs", G_VARIANT_TYPE ("as"));
100 : :
101 [ # # ]: 0 : if (uuids != NULL)
102 : : {
103 [ # # ]: 0 : g_clear_pointer (&self->uuids, g_strfreev);
104 : 0 : self->uuids = g_variant_dup_strv (uuids, NULL);
105 : : }
106 : : }
107 : :
108 : : /*
109 : : * GObject
110 : : */
111 : : static void
112 : 2 : valent_bluez_device_constructed (GObject *object)
113 : : {
114 : 2 : ValentBluezDevice *self = VALENT_BLUEZ_DEVICE (object);
115 : :
116 : 2 : G_OBJECT_CLASS (valent_bluez_device_parent_class)->constructed (object);
117 : :
118 : 4 : self->properties_changed_id =
119 : 2 : g_dbus_connection_signal_subscribe (self->connection,
120 : : BLUEZ_NAME,
121 : : "org.freedesktop.DBus.Properties",
122 : : "PropertiesChanged",
123 : 2 : self->object_path,
124 : : BLUEZ_DEVICE_NAME,
125 : : G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
126 : : on_properties_changed,
127 : 2 : weak_ref_new (object),
128 : : weak_ref_free);
129 : 2 : }
130 : :
131 : : static void
132 : 2 : valent_bluez_device_dispose (GObject *object)
133 : : {
134 : 2 : ValentBluezDevice *self = VALENT_BLUEZ_DEVICE (object);
135 : :
136 : 2 : g_cancellable_cancel (self->cancellable);
137 [ + - ]: 2 : if (self->properties_changed_id > 0)
138 : : {
139 : 2 : g_dbus_connection_signal_unsubscribe (self->connection,
140 : : self->properties_changed_id);
141 : 2 : self->properties_changed_id = 0;
142 : : }
143 : :
144 : 2 : G_OBJECT_CLASS (valent_bluez_device_parent_class)->dispose (object);
145 : 2 : }
146 : :
147 : : static void
148 : 2 : valent_bluez_device_finalize (GObject *object)
149 : : {
150 : 2 : ValentBluezDevice *self = VALENT_BLUEZ_DEVICE (object);
151 : :
152 [ + - ]: 2 : g_clear_object (&self->cancellable);
153 [ + - ]: 2 : g_clear_object (&self->connection);
154 [ + - ]: 2 : g_clear_pointer (&self->object_path, g_free);
155 [ + - ]: 2 : g_clear_pointer (&self->uuids, g_strfreev);
156 : :
157 : 2 : G_OBJECT_CLASS (valent_bluez_device_parent_class)->finalize (object);
158 : 2 : }
159 : :
160 : : static void
161 : 0 : valent_bluez_device_get_property (GObject *object,
162 : : guint prop_id,
163 : : GValue *value,
164 : : GParamSpec *pspec)
165 : : {
166 : 0 : ValentBluezDevice *self = VALENT_BLUEZ_DEVICE (object);
167 : :
168 [ # # # ]: 0 : switch ((ValentBluezDeviceProperty)prop_id)
169 : : {
170 : 0 : case PROP_CONNECTION:
171 : 0 : g_value_set_object (value, self->connection);
172 : 0 : break;
173 : :
174 : 0 : case PROP_OBJECT_PATH:
175 : 0 : g_value_set_string (value, self->object_path);
176 : 0 : break;
177 : :
178 : 0 : default:
179 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
180 : : }
181 : 0 : }
182 : :
183 : : static void
184 : 4 : valent_bluez_device_set_property (GObject *object,
185 : : guint prop_id,
186 : : const GValue *value,
187 : : GParamSpec *pspec)
188 : : {
189 : 4 : ValentBluezDevice *self = VALENT_BLUEZ_DEVICE (object);
190 : :
191 [ + + - ]: 4 : switch ((ValentBluezDeviceProperty)prop_id)
192 : : {
193 : 2 : case PROP_CONNECTION:
194 : 2 : self->connection = g_value_dup_object (value);
195 : 2 : break;
196 : :
197 : 2 : case PROP_OBJECT_PATH:
198 : 2 : self->object_path = g_value_dup_string (value);
199 : 2 : break;
200 : :
201 : 0 : default:
202 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
203 : : }
204 : 4 : }
205 : :
206 : : static void
207 : 1 : valent_bluez_device_class_init (ValentBluezDeviceClass *klass)
208 : : {
209 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
210 : :
211 : 1 : object_class->constructed = valent_bluez_device_constructed;
212 : 1 : object_class->dispose = valent_bluez_device_dispose;
213 : 1 : object_class->finalize = valent_bluez_device_finalize;
214 : 1 : object_class->get_property = valent_bluez_device_get_property;
215 : 1 : object_class->set_property = valent_bluez_device_set_property;
216 : :
217 : : /**
218 : : * ValentBluezDevice:connection
219 : : *
220 : : * The D-Bus connection.
221 : : */
222 : 2 : properties [PROP_CONNECTION] =
223 : 1 : g_param_spec_object ("connection", NULL, NULL,
224 : : G_TYPE_DBUS_CONNECTION,
225 : : (G_PARAM_READWRITE |
226 : : G_PARAM_CONSTRUCT_ONLY |
227 : : G_PARAM_EXPLICIT_NOTIFY |
228 : : G_PARAM_STATIC_STRINGS));
229 : :
230 : : /**
231 : : * ValentBluezDevice:object-path
232 : : *
233 : : * The D-Bus object path of the device.
234 : : */
235 : 2 : properties [PROP_OBJECT_PATH] =
236 : 1 : g_param_spec_string ("object-path", NULL, NULL,
237 : : NULL,
238 : : (G_PARAM_READWRITE |
239 : : G_PARAM_CONSTRUCT_ONLY |
240 : : G_PARAM_EXPLICIT_NOTIFY |
241 : : G_PARAM_STATIC_STRINGS));
242 : :
243 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
244 : 1 : }
245 : :
246 : : static void
247 : 2 : valent_bluez_device_init (ValentBluezDevice *device)
248 : : {
249 : 2 : device->cancellable = g_cancellable_new ();
250 : 2 : }
251 : :
252 : : /**
253 : : * valent_bluez_device_new:
254 : : * @connection: a `GDBusConnection`
255 : : * @object_path: An object path
256 : : * @props: a `GVariant`
257 : : *
258 : : * Create a new `ValentBluezDevice` on @connection for @object_path. If @properties
259 : : * is not %NULL, the returned proxy will be populated.
260 : : *
261 : : * Returns: (transfer full): a new `ValentBluezDevice`
262 : : */
263 : : ValentBluezDevice *
264 : 2 : valent_bluez_device_new (GDBusConnection *connection,
265 : : const char *object_path,
266 : : GVariant *props)
267 : : {
268 : 2 : ValentBluezDevice *ret;
269 : 4 : g_autoptr (GVariant) uuids = NULL;
270 : :
271 [ + - + - : 2 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
- + - - ]
272 [ + - ]: 2 : g_return_val_if_fail (g_variant_is_object_path (object_path), NULL);
273 [ + - + - ]: 2 : g_return_val_if_fail (props == NULL || g_variant_is_of_type (props, G_VARIANT_TYPE ("a{sv}")), NULL);
274 : :
275 : 2 : ret = g_object_new (VALENT_TYPE_BLUEZ_DEVICE,
276 : : "connection", connection,
277 : : "object-path", object_path,
278 : : NULL);
279 : :
280 : 2 : g_variant_lookup (props, "Connected", "b", &ret->connected);
281 : 2 : g_variant_lookup (props, "Paired", "b", &ret->paired);
282 : :
283 : 2 : uuids = g_variant_lookup_value (props, "UUIDs", G_VARIANT_TYPE ("as"));
284 [ + - ]: 2 : if (uuids != NULL)
285 : 2 : ret->uuids = g_variant_dup_strv (uuids, NULL);
286 : :
287 : 2 : return ret;
288 : : }
289 : :
290 : : static void
291 : 0 : valent_bluez_device_connect_cb (GDBusConnection *connection,
292 : : GAsyncResult *result,
293 : : ValentBluezDevice *self)
294 : : {
295 : 0 : g_autoptr (GVariant) reply = NULL;
296 [ # # ]: 0 : g_autoptr (GError) error = NULL;
297 : :
298 : 0 : reply = g_dbus_connection_call_finish (connection, result, &error);
299 [ # # ]: 0 : if (reply != NULL)
300 : : return;
301 : :
302 [ # # ]: 0 : if (g_dbus_error_is_remote_error (error))
303 : : {
304 : 0 : g_autofree char *remote_error = NULL;
305 : :
306 : 0 : remote_error = g_dbus_error_get_remote_error (error);
307 [ # # # # ]: 0 : if (g_strcmp0 (remote_error, "org.bluez.Error.AlreadyConnected") == 0 ||
308 : 0 : g_strcmp0 (remote_error, "org.bluez.Error.InProgress") == 0)
309 : 0 : return;
310 : : }
311 : :
312 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
313 : : {
314 : 0 : g_dbus_error_strip_remote_error (error);
315 : 0 : g_warning ("Failed to connect to %s: %s",
316 : : self->object_path,
317 : : error->message);
318 : : }
319 : : }
320 : :
321 : : /**
322 : : * valent_bluez_device_connect:
323 : : * @device: A `ValentBluezDevice`
324 : : *
325 : : * Attempt to connect @device.
326 : : */
327 : : void
328 : 2 : valent_bluez_device_connect (ValentBluezDevice *device)
329 : : {
330 [ - + ]: 2 : g_return_if_fail (VALENT_IS_BLUEZ_DEVICE (device));
331 : :
332 [ + - + - ]: 2 : if (!device->paired || device->uuids == NULL)
333 : : return;
334 : :
335 [ - + ]: 2 : if (!g_strv_contains ((const char * const *)device->uuids,
336 : : VALENT_BLUEZ_PROFILE_UUID))
337 : : return;
338 : :
339 : 0 : g_dbus_connection_call (device->connection,
340 : : BLUEZ_NAME,
341 : 0 : device->object_path,
342 : : BLUEZ_DEVICE_NAME,
343 : : "ConnectProfile",
344 : : g_variant_new ("(s)", VALENT_BLUEZ_PROFILE_UUID),
345 : : NULL,
346 : : G_DBUS_CALL_FLAGS_NONE,
347 : : -1,
348 : : device->cancellable,
349 : : (GAsyncReadyCallback)valent_bluez_device_connect_cb,
350 : : device);
351 : : }
352 : :
|